背景
DBA 又把建表語(yǔ)句打回來(lái)了箩朴,原因是字段可空了!理由是為了省空間!G锒取Uㄅ印!
語(yǔ)句如下:
CREATE TABLE user_info (
id bigint unsigned NOT NULL AUTO_INCREMENT,
user_id int NOT NULL COMMENT '用戶id',
user_name varchar(64) NOT NULL COMMENT '真實(shí)姓名',
email varchar(30) NOT NULL COMMENT '用戶郵箱',
nick_name varchar(45) COMMENT '昵稱',
status tinyint NOT NULL COMMENT '用戶狀態(tài)荚斯,1-正常埠居,2-注銷,3-凍結(jié)',
address varchar(128) COMMENT '家庭住址',
--省略了一些屬性事期,篇幅原因滥壕,這里刪掉了
PRIMARY KEY (id),
KEY idx_user_id (user_id)
) ENGINE=InnoDB COMMENT='用戶基本信息';
用戶可能就是沒(méi)有昵稱啊,設(shè)置為可空兽泣,看起來(lái)也沒(méi)啥問(wèn)題耙镩佟!有沒(méi)有一種被針對(duì)的感覺(jué)
其實(shí)你如果知道每條記錄真正的存儲(chǔ)格式唠倦,你就知道怎么懟他了金踪。
正文
InnoDB 頁(yè)的概念
Page 是 Innodb 存儲(chǔ)的最基本結(jié)構(gòu),也是 Innodb 磁盤管理的最小單位牵敷,與數(shù)據(jù)庫(kù)相關(guān)的所有內(nèi)容都存儲(chǔ)在 Page 結(jié)構(gòu)里。
MySQL 的客戶端和服務(wù)端的交互最小單位也是 Innodb 頁(yè)(默認(rèn)大小為 16K)法希,也就是說(shuō)如果你即使只查詢一條記錄枷餐,一次至少?gòu)拇疟P上讀取 16K 的內(nèi)容到內(nèi)存中。
Innodb 行格式
我們平時(shí)往數(shù)據(jù)庫(kù)插入一條條的記錄苫亦,這些記錄在磁盤上存放格式也是以行記錄的方式的毛肋。
Innodb 存儲(chǔ)引擎目前有四種格式--COMPACT、REDUNDANT屋剑、DYNAMIC润匙、COMPRESSED
從 MySQL5.7 開始,默認(rèn)是 DYNAMIC 方式唉匾,我們用圖片來(lái)看一下 Innodb 行格式大概示意圖孕讳。(這里以 COMPACT為例匠楚,因?yàn)檫@ 2 者基本上是一樣的,只有一些細(xì)微的差別)
記錄額外信息
這里只講變長(zhǎng)屬性長(zhǎng)度列表厂财、NULL 值列表 2 個(gè)部分
1芋簿、變長(zhǎng)屬性長(zhǎng)度列表
常見的關(guān)系型數(shù)據(jù)庫(kù)都支持變長(zhǎng)屬性,比如上面的語(yǔ)句username varchar(64)中璃饱,64 是代表可存儲(chǔ)的最大的字符數(shù)与斤,如果某條記錄的名字是“張三”,會(huì)有什么樣的問(wèn)題呢
我們要讓 MySQL 服務(wù)器知道每條記錄的每個(gè)變長(zhǎng)屬性知道實(shí)際長(zhǎng)度是多少荚恶,不然它會(huì)懵
我們來(lái)舉一個(gè)栗子:
上面的記錄 user_name,email,nick_name,address 都是 varchar 類型的撩穿,那么他存儲(chǔ)的記錄格式是什么樣的呢
上圖中的 09、0b谒撼、06食寡、09 分別對(duì)應(yīng) user_name,email,nick_name、address 對(duì)應(yīng)字節(jié)長(zhǎng)度嗤栓,16 進(jìn)制(實(shí)際上無(wú)空格冻河,這里只是為了方便看)。
至于為什么是倒序放的茉帅,不是本文重點(diǎn)叨叙,這里先不做解答
小彩蛋:varchar(M)中的 M 代表能存儲(chǔ)的最大字符數(shù),實(shí)際存儲(chǔ)時(shí)都是按照字節(jié)來(lái)計(jì)算的堪澎,所以一個(gè)字段占用的實(shí)際字節(jié)數(shù)的公式:
“
RL(存儲(chǔ)屬性最多的字節(jié)數(shù))=M(最多字符數(shù))* W(當(dāng)前字符集下最多需要幾個(gè)字節(jié)表示一個(gè)字符)
”
所以在編碼是 utf8mb4 的情況下擂错,user_name 最多占用的字節(jié)數(shù)=64 * 4 = 256,所以該屬性需要 2 個(gè)字節(jié)表示長(zhǎng)度
為什么不是 1 個(gè)字節(jié)樱蛤,1 個(gè)字節(jié)不正好是 256 嗎钮呀?因?yàn)榈谝晃皇?b>標(biāo)記位,如果第一位是 0昨凡,表示用 1 個(gè)字節(jié)就可以表示該屬性的長(zhǎng)度爽醋,如果第一位是 1,表示需要用 2 個(gè)字節(jié)表示該屬性的長(zhǎng)度便脊。
但是如果RL大于256蚂四,占用的字節(jié)也是2個(gè),字段屬性值保存一部分或者(數(shù)據(jù)地址)
如果另一條記錄是這樣的:
則對(duì)應(yīng)的行格式如下:
昵稱哪痰、家庭住址為空遂赠,則不在變長(zhǎng)長(zhǎng)度列表中出現(xiàn)。
2晌杰、NULL 值列表
我們?cè)诓迦胍粭l記錄時(shí)跷睦,Innodb 引擎怎么處理
先統(tǒng)計(jì)允許為 NULL 的列有哪些
如果建表語(yǔ)句中,沒(méi)有可空的字段肋演,就沒(méi)有 NULL 值列表抑诸;如果有可空的字段烂琴,那么每個(gè)可空字段對(duì)應(yīng)一個(gè)二進(jìn)制位,1-代表 NULL哼鬓,0-不為 NULL
最小單位需要用字節(jié)來(lái)表示 NULL 值列表监右,如果只有 2 個(gè)可空值,至少也需要 1 個(gè)字節(jié)來(lái)表示异希,高位補(bǔ) 0健盒,如張無(wú)忌對(duì)應(yīng)的 NULL 值列表為 00000011,張三豐對(duì)應(yīng)的 NULL 值列表為 00000000
張三豐對(duì)應(yīng)的記錄行格式(00000000):
張無(wú)忌對(duì)應(yīng)的記錄行格式(00000011):
綜上
如果一張表沒(méi)有可空的字段称簿,比如建表語(yǔ)句如下:
語(yǔ)句如下:
CREATE TABLE user_info (
id bigint unsigned NOT NULL AUTO_INCREMENT,
user_id int NOT NULL COMMENT '用戶id',
user_name varchar(64) NOT NULL COMMENT '真實(shí)姓名',
email varchar(30) NOT NULL COMMENT '用戶郵箱',
nick_name varchar(45) NOT NULL COMMENT '昵稱',
status tinyint NOT NULL COMMENT '用戶狀態(tài)扣癣,1-正常,2-注銷憨降,3-凍結(jié)',
address varchar(128) NOT NULL COMMENT '家庭住址',
--省略了一些屬性父虑,篇幅原因,這里刪掉了
PRIMARY KEY (id),
KEY idx_user_id (user_id)
) ENGINE=InnoDB COMMENT='用戶基本信息';
對(duì)應(yīng)的行結(jié)構(gòu)如下:
沒(méi)有 NULL 值列表哪一項(xiàng)授药,但是雖然 NULL 列表中沒(méi)有了士嚎,變長(zhǎng)列表中會(huì)有,同時(shí)真實(shí)數(shù)據(jù)部分也會(huì)有悔叽,占用的空間更大莱衩,可見為了省空間這種說(shuō)法站不住腳。
總結(jié)
原因到底是為什么呢娇澎?
MySQL 官方有那么一句話:
NULL columns require additional space in the rowto record whether their values are NULL. For MyISAM tables, each NULL columntakes one bit extra, rounded up to the nearest byte.
中文意思是:
Mysql 難以優(yōu)化引用可空列查詢笨蚁,它會(huì)使索引、索引統(tǒng)計(jì)和值更加復(fù)雜趟庄±ㄏ福可空列需要更多的存儲(chǔ)空間,還需要 mysql 內(nèi)部進(jìn)行特殊處理戚啥》艿ィ可空列被索引后,每條記錄都需要一個(gè)額外的字節(jié)猫十,還能導(dǎo)致 MYisam 中固定大小的索引變成可變大小的索引览濒。
照此看來(lái),官方說(shuō)法是其中一點(diǎn)原因炫彩,站在Innodb的行格式上,不見得是省空間的絮短。
作者拙見:
所有使用 NULL 值的情況江兢,都可以通過(guò)一個(gè)有意義的值的表示,這樣有利于代碼的可讀性和可維護(hù)性丁频,并能從約束上增強(qiáng)業(yè)務(wù)數(shù)據(jù)的規(guī)范性杉允。
NULL 值到非 NULL 的更新無(wú)法做到原地更新邑贴,更容易發(fā)生索引分裂,從而影響性能叔磷。
NULL 值在 timestamp 類型下容易出問(wèn)題拢驾,特別是沒(méi)有啟用參數(shù) explicit_defaults_for_timestamp
NOT IN、!= 等負(fù)向條件查詢?cè)谟?NULL 值的情況下返回永遠(yuǎn)為空結(jié)果改基,查詢?nèi)菀壮鲥e(cuò)繁疤,不是自己想要的結(jié)果,甚至可能出現(xiàn)重大損失