為什麼不建議給MySQL設定Null值?《死磕MySQL系列 十八》

發表於2022-05-06

大家好,我是咔咔 不期速成,日拱一卒

之前ElasticSearch系列文章中提到了如何處理空值,若為Null則會直接報錯,因為在ElasticSearch中當欄位值為null時、空陣列、null值陣列時,會將其視為該欄位沒有值,最終還是需要使用exists或者null_value來處理空值

大多數ElasticSearch的資料都來自於各類資料庫,這裡暫且只針對於MySQL,各個開源軟體中都預設相容各種Null值,空陣列等等

若從根源上截斷就可以省很多事,直到現在很多開發小夥伴還是堅韌不拔的給欄位的預設值還是Null

本期就來聊一聊為什麼不建議給欄位的預設值設定為Null

本期環境為:MySQL8.0.26

null
null

一、案例資料

建立表user

CREATE TABLE `user` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
 `age` tinyint(4) unsigned NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

新增資料,共計10條資料,有兩條資料的name值為Null

INSERT INTO `user` (`name`, `age`) VALUES ('kaka', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('niuniu', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('yangyang', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('dandan', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('liuliu', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('yanyan', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('leilie', 26);
INSERT INTO `user` (`name`, `age`) VALUES ('yao', 26);
INSERT INTO `user` (`name`, `age`) VALUES (NULL, 26);
INSERT INTO `user` (`name`, `age`) VALUES (NULL, 26);

一、count資料丟失

在這期 MySQL統計總數就用count,別花裡胡哨的《死磕MySQL系列 十》 文章中,已經對count的使用說的非常明白了。

那借著這個案例,來分析一下為什麼資料會丟失,先看結果

select count(*) as num1 ,count(name) as num2 from user;

使用count欄位名時出現了資料丟失,很明顯是因為主鍵ID9、10這兩條記錄的name值為空造成的。

為什麼會出現這種情況?

當count除了主鍵欄位外,會有兩種情況:

一種是欄位為null,執行時,判斷到有可能是null,但還要把值取出來再判斷下,不是null的進行累加

另一種是欄位為not null,執行時,逐行從記錄裡邊讀出這個欄位,判斷不是null,才進行累加

此時,我們們遇到的問題是name欄位的值存在了null值,所以會走第一種情況,不進行統計null值

為什麼建議大家都使用count(*)?

MySQL對於count做了專門的優化,跟欄位不同的是並不是把所有帶了*的值取出來,而是指定了count(*)肯定不是null,只需要按行累加即可

MySQL團隊對count(*)做了什麼優化?

MySQL系列文章至今已經更新了第十八期了,你有沒有猜到原因呢?

現在你應該知道主鍵索引結構中葉子節點儲存的是整行資料,而普通索引葉子節點儲存的是主鍵ID

那對於普通索引來說肯定會比主鍵索引小,因為對於MySQL來說,不管遍歷哪個索引結果都一樣,所以優化器會主動去找到那顆最小的樹進行遍歷。

在邏輯正確的前提下,儘量減少訪問資料量,是資料庫系統設計通用法則之一。

最後給大家留一個問題,為什麼Innodb儲存引擎不跟Myisam儲存一樣儲存一個count值呢?

如果不知道的話,可以看上文提到的count文章

二、為distinct打抱不平

在開發工作中使用Distinct進行去重的場景十分的少,大多數情況都是使用group by完成的

select distinct name from user;

可以看到此時的資料依然是正確的,對Null值做了去重的操作

為什麼要說這個,因為咔咔在其它的平臺上看到過有人這麼使用count(distinct name,mobile),然後說是統計出來的資料不準確。

這種用法依然是count(欄位)的用法,distinct本身是會對Null進行去重,去重後依然是需要判斷name的值不為null時,才會進行累計。

所以,不要把鍋甩給distinct

三、使用表示式資料丟失

在一些值為null時,使用表示式會造成資料的不一致,接下來一起看下

select * from user where name != 'kaka';
表示式造成資料丟失
表示式造成資料丟失

這跟我們的預期結果不大一致,預期是想返回id2~10的資料

當然,這個問題也不是無解,MySQL同樣也提供了方法

要解決這個問題,只能再加一個條件就是把欄位值為null的再單獨處理一下

isnull
isnull

四、空指標問題

如果一個列存在null值,使用MySQL的聚合函式後返回結果是null,而並非是0,就會造成程式執行時的指標異常

CREATE TABLE user_order (
 id INT PRIMARY KEY auto_increment,
 num int
) ENGINE='innodb';
insert into user_order(num) values(3),(6),(6),(NULL);

建立使用者訂單數量表,並插入4條資料,接下來演示一下產生的問題

select sum(num) from goods where id>4;
聚合函式產生的問題
聚合函式產生的問題

可以看到當欄位為null時,使用聚合函式返回值就是null,並非是0,那麼這個問題要怎麼處理呢?

同樣MySQL也給大家提供了對應函式,就是ifnull

select ifnull(sum(num), 0) from goods where id>4;
在這裡插入圖片描述
在這裡插入圖片描述

五、這是在難為誰?

當一個欄位的值存在null值,若要進行null值查詢時,必須要使用isnull或者ifnull進行匹配查詢,又或者使用is null,is not null。

而常用的表示式就不能再進行使用了,有工作經驗的還好的,要是新人的話會很難受。

接下來看幾個新人經常犯的錯誤

錯誤一

對存在null值的欄位使用表示式進行過濾,正確用法應該是is null 或者 is not null

select * from user where name<>null;
在這裡插入圖片描述
在這裡插入圖片描述

錯誤二

依然是使用表示式,同樣可以使用isnull

在這裡插入圖片描述
在這裡插入圖片描述

六、總結

說了這麼多也都感覺到了欄位設定為null的麻煩之處,不過幸好的是MySQL對使用is null、isnull()等依然可以使用上索引。

咔咔目前所在的公司存在大量欄位預設值就是null,於是程式碼中就大量儲存ifnull、is null、is not null等程式碼。

一般欄位數值型別的預設值就給成0,字串的給個空也行,千萬不要給null了哈!

推薦閱讀

死磕MySQL系列總目錄

闖禍了,生成環境執行了DDL操作《死磕MySQL系列 十四》

聊聊MySQL的加鎖規則《死磕MySQL系列 十五》

為什麼不讓用join?《死磕MySQL系列 十六》

MySQL對JOIN做了那些不為人知的優化《死磕MySQL系列 十七》

堅持學習、堅持寫作、堅持分享是咔咔從業以來所秉持的信念。願文章在偌大的網際網路上能給你帶來一點幫助,我是咔咔,下期見。

相關文章