前言
在表結(jié)構(gòu)設(shè)計(jì)時(shí)我們經(jīng)常會(huì)面對(duì)是否要加空值約束墨叛、默認(rèn)值處理等問題石窑,當(dāng)向前人經(jīng)驗(yàn)中檢索時(shí)炫欺,經(jīng)常會(huì)看到不建議為null懊纳,強(qiáng)刷面經(jīng)時(shí)not null也經(jīng)常被歸結(jié)到優(yōu)化建議當(dāng)中去揉抵,今天就來看一下為何?
通常來說嗤疯,null表示虛無&不確定冤今,在不同的實(shí)現(xiàn)中對(duì)null的定義相似。最早在Codd提出關(guān)系模型的最早的paper里茂缚,就引入了NULL戏罢。
先看不同語(yǔ)言對(duì)于null的處理:
c++的NULL
c++中NULL是一個(gè)宏,是一個(gè)空指針常量脚囊,如果將NULL擴(kuò)展為常數(shù)龟糕,那么這個(gè)數(shù)是0,類型為int悔耘,也就是說常數(shù)0既是整型常量讲岁,也是空指針常量(cpp 11中引入了nullptr作為空指針常量,解決二義性)衬以。
Java的null
Java中的null是一個(gè)關(guān)鍵字缓艳,通常指的是非基礎(chǔ)類型變量的引用指向一個(gè)“空”,可以理解為引用對(duì)象為不確定泄鹏,Java是一種基于copy傳遞的基礎(chǔ)類型+引用類型數(shù)據(jù)構(gòu)成的對(duì)象體系郎任,Java內(nèi)存管理中使用句柄來操控對(duì)象,而null表示句柄所在的內(nèi)存地址上沒有存儲(chǔ)任何真實(shí)對(duì)象的地址备籽。
go的nil
go中沒有null這個(gè)關(guān)鍵字(任何類型都會(huì)零值處理),但是有一個(gè)類似的nil(注意不是關(guān)鍵字),跟null的定位類似但不是完全相同车猬,是一個(gè)預(yù)先定義的標(biāo)識(shí)符霉猛,通常用于指針類型、map珠闰、slice惜浅、function、channel伏嗜、interface等類型的零值定義坛悉,是一個(gè)沒有默認(rèn)類型的空值,標(biāo)示的并非不確定性承绸,而是可以明確代表各種不同內(nèi)存布局的類型的零值裸影。
比如說:
fmt.Println( (interface{})(nil) == (*int)(nil) )
// print:false
//下面這樣聲明零值后是 零值間是無法比較的
var _ = ([]int)(nil) == ([]int)(nil)
var _ = (map[string]int)(nil) == (map[string]int)(nil)
var _ = (func())(nil) == (func())(nil)
然后來看存儲(chǔ)中對(duì)null的定義:
redis中的空值
redis中操作時(shí)都是針對(duì)明確的值的,不會(huì)出現(xiàn)像是不確定這樣的定義军熏,通常來說轩猩,所以在操作redis時(shí)是沒有對(duì)null的數(shù)據(jù)定義的,但是會(huì)有nil這樣的零值存在荡澎,跟go處理方式比較像均践,代表n個(gè)結(jié)構(gòu)的零值,一些語(yǔ)言對(duì)于nil return這種情況有時(shí)候會(huì)變成一種null來處理摩幔,因?yàn)閷?duì)于client來說彤委,這就是個(gè)不確定的返回或者空值。
127.0.0.1:6379> get B
(nil)
說到redis了或衡,也順道看下lua對(duì)于空返回的處理焦影,容易寫出bug來,簡(jiǎn)單記錄了下薇宠,與本章主題無關(guān)偷办。官方文檔中的:
A non-nil Redis "bulk reply" results in a Lua string as the return value.
A nil bulk reply results in a ngx.null return value
mysql的空值定義
看到各種語(yǔ)言、工具中對(duì)null澄港、nil等關(guān)鍵詞的使用椒涯,不難發(fā)現(xiàn) null代表的是一種內(nèi)存存儲(chǔ)的不確定性,nil代表的通常是一種約定的零值回梧。
mysql中也是如此废岂,null代表的是一種不確定性,所以通常用is null 或者not null來判定一個(gè)實(shí)例數(shù)據(jù)是否為不確定的狱意,而不是直接==來進(jìn)行值比較湖苞。
官方手冊(cè)中中的定義:
The NULL value means “no data.” NULL can be written in any lettercase
也就產(chǎn)生了下面的關(guān)系操作符:
IS NULL\IS NOT NULL:能正常比較
<=>:結(jié)果都是false
mysql 這樣對(duì)null的定義導(dǎo)致了一些問題:
1、誤導(dǎo)性&sql操作時(shí)的疏忽
select A from t_xx where B not null or B = 2\G #正常操作
select A from t_xx where B = null or B = 2\G # B = null條件異常
select * from tableA where xxx not in(1, 2, null)\G #返回永遠(yuǎn)為空
select count(*) from tableA\G #null不參與統(tǒng)計(jì)
2详囤、另外财骨,null值在timestamp類型下容易出問題镐作,特別是沒有啟用參數(shù)explicit_defaults_for_timestamp
mysql null的空間表現(xiàn)
null在mysql中是一種特殊的“占位符”,用來表示不確定性隆箩,但是實(shí)際上它也是需要占用一部分內(nèi)存空間的该贾,比不是所想的會(huì)省內(nèi)存。
null的長(zhǎng)度是null,Compact Row Format前提下捌臊,每個(gè)行記錄都會(huì)有一個(gè)Bit vector來記錄行中出現(xiàn)NULL的字段杨蛋,長(zhǎng)度為 N / 8 向上取整,其中 N為值NULL的字段數(shù)理澎。
select length(''), length(null), length(0), length('0');
+------------+--------------+-----------+-------------+
| length('') | length(null) | length(0) | length('0') |
+------------+--------------+-----------+-------------+
| 0 | NULL | 1 | 1 |
+------------+--------------+-----------+-------------+
對(duì)于變長(zhǎng)字段是可以起到一定的省空間的作用逞力,對(duì)于int/char這些是毫無作用的,并且需要一個(gè)額外字節(jié)作為判斷是否為null的標(biāo)志位(這個(gè)是問題糠爬,但忽略不計(jì))
某些情況可以省寇荧,但是相對(duì)于它帶來的其他影響,這點(diǎn)優(yōu)勢(shì)啥都不是秩铆,繼續(xù)往下看
mysql null對(duì)索引的影響
Mysql難以優(yōu)化引用可空列查詢砚亭,它會(huì)使索引、索引統(tǒng)計(jì)和值更加復(fù)雜殴玛⊥北欤可空列需要更多的存儲(chǔ)空間,還需要mysql內(nèi)部進(jìn)行特殊處理滚粟⊙罢蹋可空列被索引后,每條記錄都需要一個(gè)額外的字節(jié)凡壤,還能導(dǎo)致MYisam 中固定大小的索引變成可變大小的索引署尤。 --《高性能mysql》
1、雖然mysql會(huì)對(duì)null字段也進(jìn)行索引亚侠,但是只有is null的方式會(huì)使用上索引,所以一旦使用不好曹体,索引就無效了。
2硝烂、如果null列做了唯一索引箕别,那就尷尬了:唯一索引字段允許插入多條null的記錄
3、null一定程度上會(huì)是key_len變長(zhǎng)(key_len 的計(jì)算規(guī)則和三個(gè)因素有關(guān):數(shù)據(jù)類型滞谢、字符編碼串稀、是否為null,上面也提到啦)
4、mysql內(nèi)部會(huì)對(duì)null 做很多特殊邏輯的處理影響性能狮杨。
總結(jié)
1母截、使用上容易出錯(cuò)
2、很容易導(dǎo)致索引不可用或者效率下降
3橄教、很多情況下會(huì)增加存儲(chǔ)
4清寇、提升表的維護(hù)成本
所有使用NULL值的情況喘漏,都可以通過一個(gè)有意義的值的表示,這樣有利于代碼的可讀性和可維護(hù)性颗管。