大家好,我是咔咔 不期速成,日拱一卒
之前ElasticSearch系列文章中提到了如何處理空值,若為Null則會直接報錯,因為在ElasticSearch中當欄位值為null時、空陣列、null值陣列時,會將其視為該欄位沒有值,最終還是需要使用exists
或者null_value
來處理空值
大多數ElasticSearch的資料都來自於各類資料庫,這裡暫且只針對於MySQL,各個開源軟體中都預設相容各種Null值,空陣列等等
若從根源上截斷就可以省很多事,直到現在很多開發小夥伴還是堅韌不拔的給欄位的預設值還是Null
本期就來聊一聊為什麼不建議給欄位的預設值設定為Null
本期環境為:MySQL8.0.26
一、案例資料
建立表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的再單獨處理一下
四、空指標問題
如果一個列存在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了哈!
推薦閱讀
闖禍了,生成環境執行了DDL操作《死磕MySQL系列 十四》
MySQL對JOIN做了那些不為人知的優化《死磕MySQL系列 十七》
“堅持學習、堅持寫作、堅持分享是咔咔從業以來所秉持的信念。願文章在偌大的網際網路上能給你帶來一點幫助,我是咔咔,下期見。
”