我們知道使用 MySQl 去存儲數據,我們需要先去建表,那麼在表中去添加各個數據列,其實我們需要給數據列指定一些屬性,比如說我們在列屬性設置為允許,那就可以不放置任何數據到這一列中。但是這會帶來哪些麻煩或者是坑呢?那麼帶着這個問題,我們來聊聊表屬性設置為 NULL,你可能要面臨很多麻煩。
第一點,NULL 它是一個默認行為,如果你不去主動指定這個列字段是 NOT NULL,那麼它就是 NULL,這其實是允許 NULL 列最常見的原因了。很多剛開始學習使用 MySQL 的同學,沒有顯示的去指定當前列是 NOT NULL ,那麼 MySQL 會自動的設置為當前列是允許 NULL 的。
第二點,有一個很嚴重的誤區,就是很多初學者會認為 NULL 它代表的是空,也就是 Mysql 什麼都不會存儲,所以使用那可以節省存儲空間, 但是實際上這是一個誤區,那它是會占用存儲空間的。
第三點,NULL 屬性非常方便,SQL 語句或者代碼不需要額外的填充或判斷。這個說法看起來非常合理,我們在操作數據時,如果某一列是空值,那麼無論是 SQL 語句還是代碼,我們都不需要去考慮它。
但是即使是有這麼多原因,這麼多人使用允許 NULL 的屬性列,我們似乎還是會經常聽到,那麼使用 NULL 列并不好,那麼我們到底應該去怎樣做選擇呢?也就是去應該聽誰的呢?
我們當然不能僅憑聽說去認證哪種選擇會更好,也不能認為使用的人多就是對的。那麼為了搞清楚這個問題,我們可以看看官網對允許 NULL 列的一個描述。
在 MySQL 的官網上可以看到如下的解釋:
NULL columns require additional space in the rowto record whether their values areNULL.For MyISAM tables, each NULL columntakes one bit extra, rounded up to thenearest byte.
翻譯過來就是:MySQL 難以優化引用可空列的一個查詢,它會使索、引索引統計和值更加複雜。可空的列需要更多的存儲空間,還需要 MySQL内部進行特殊處理。可空列被索引之後,每條記錄都需要一個額外的字節,它還會導緻像 MySAM 中固定大小的索引變成可變大小的索引。
我們從官網這一段描述中就可以看出來,其實 NULL 列它不僅僅會占用額外的存儲空間,而且會對索引優化有影響。
總結下來就是說,MySQL 并不建議我們去允許 NULL 列。那麼究竟 NULL 列會對我們的業務邏輯造成哪些影響呢?接下來我們就去探究一下這個問題。
我們一直在說 NULL 列實際上是占據空間的,我們可以去執行一個 SQL 語句去查看一下。
我們打開 MySQL 客戶端,我們可以去通過 select,使用MySQL 中的 length 函數,比如說我們可以去看一看空字符串所占據的空間,看一看 NULL 所占據的空間,int 類型的數字占據的内存空間,字符串零所占據的内存空間。

注意:MySQL 數據庫是不區分大小寫的。
我們可以看到 MySQL打印的這個 NULL 的長度是 NULL 而不是零。這其實是 MySQL 做的一個特殊處理,或者是叫标記。
以此也可以得出結論,那麼為了标識這一列是 NULL,需要額外的存儲空間,至少它并不是零。如果是零的話,它會直接打印零。
接下來為了更方便的去演示 NULL 列參與的查詢、計算以及這個聚合等等操作,我們先去創建一張數據表,并且插入一些演示數據。
我已經準備了一張數據表以及一些演示數據,這些數據我都會提供給大家。
創建數據庫。
create database db_mysql_escape;

創建完了之後,切換當前的數據庫。
use db_mysql_escape;

創建數據庫表。
CREATE TABLE `db_mysql_escape`.`t_do_not_use_null` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`one` varchar(10) NOT NULL,
`two` varchar(20) DEFAULT NULL,
`three` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_one` (`one`),
KEY `idx_two` (`two`),
UNIQUE KEY `idx_three` (`three`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
創建成功,我們去看一看這個表的一些屬性列。

這張表結果非常簡單,除了這個主鍵 id 之外,它隻有三個數據列,我們分别叫做 one、two、three 這三個字段。它們的 type 類型都是 varcher 的類型。其中對于這個 one 字段,它是不允許空的,也就是不允許為 NULL。對于 two 和 three 我們都沒有寫到的,所以它是遵循 MySQL 的一個默認的數據列的性質,也就是為 NULL,是一個默認的。
此外,我們去跟這張表定制了一些索引,從建表語句可以看到 one 和 two 都有索引,three 是有一個唯一性索引的,這就是關于這張表的一些這個基本屬性。三個索引以及三個字段,兩個字段是允許 NULL,一個字段是不允許 NULL。
插入演示數據。
-- 初始化一些數據
INSERT INTO `db_mysql_escape`.`t_do_not_use_null`(`id`, `one`, `two`, `three`) VALUES (1, '', 'a2', 'a3');
INSERT INTO `db_mysql_escape`.`t_do_not_use_null`(`id`, `one`, `two`, `three`) VALUES (2, 'b1', NULL, 'b3');
INSERT INTO `db_mysql_escape`.`t_do_not_use_null`(`id`, `one`, `two`, `three`) VALUES (3, 'c1', 'c2', NULL);
INSERT INTO `db_mysql_escape`.`t_do_not_use_null`(`id`, `one`, `two`, `three`) VALUES (4, 'd1', 'd2', NULL);
執行查看插入數據 SQL 語句。

查看數據。
select * from t_do_not_use_null;

這些數據也非常簡單,這裡這個為空的地方它不是 NULL,如果是 NULL 的話, MySQL 會直接打印的是 NULL。
這裡的 one 插入的數據其實是空字符串,我們去看一看 insert 語句,這裡是一個空字符串,這裡插入數據的地方如果是 NULL,MySQL 會顯示 NULL,非常簡單的四條數據。
NULL 參與的查詢存在的問題是,如果你要查詢的某個字段為 NULL 或者是不為 NULL。那我們不能使用等于号或者是不等于号,而需要去使用 IS NULL 和 IS NOT NULL 去做判斷。
案例演示,我們給大家去演示一下。
比如對于 two 來說,id 為 2 的這行數據是 NULL。我們嘗試去通過等于号或者是不等于去實現查詢。
使用 = 查詢 NULL 的結果。
select * from t_do_not_use_null where two = null;
查詢結果。

可以看到它返回了一個空的數據集,也就是我們通過這個等于号,NULL 的結果是查不到的。
那麼看一看,我們通過不等于再去查詢,那麼我們可以猜想,不等 NULL ,是否會返回這個 id 為 1、2和 3 的數據呢?
使用 != 查詢 NULL 。
select * from t_do_not_use_null where two != null;

通過案例演示,返回也空結果。
這就是關于 NULL 參與查詢的一個坑。如果你使用 = 或者是 != ,你所查詢的數據都得不到。此時,我們需要将!=換成 IS NOT NULL。
使用 IS NOT NULL 查詢 NULL
select * from t_do_not_use_null where two is not null;
查詢結果

可以看到這裡返回的是 id 為 1、3 和 4 的數據。
如果需要查詢 two 字段中某條記錄為 NULL,此時我們需要使用 IS NULL。
使用 IS NULL 查詢記錄為 NULL
select * from t_do_not_use_null where two is null;
使用 IS NULL 查詢返回的結果如下。

我們可以看到返回了這個 id 為 2 的這樣一條記錄。這就是關于 NULL 列的一個參與的一個查詢。
我們再去看一個查詢,假設我們此時将條件改為 two != 'e'。想想此時會得到什麼樣的結果呢?
select * from t_do_not_use_null where two != 'e';
查詢得到的結果如下。

可以看到,當 two !='e' 的時候,返回的仍然是1、3、4 這個三條記錄,它與這個 IS NOT NULL 返回的結果是相等的。
其實關于這個 where two 使用到 !=這個符号去判斷符号之後,它其實是等價于 where two != 'e' and IS NOT NULL 。
select * from t_do_not_use_null where two != 'e' and two IS NOT NULL;
查詢的結果如下圖所示。

一定需要理解對于這個 NULL 列參與的查詢一定是使用到 IS NULL 或者是 IS NOT NULL。
如果 where 中,你使用了這樣一個 = 号或者是 != 号,它都會給你加上某一個字段 IS NOT NULL 這樣的一個屬性,這就是關于 NULL 參與查詢的一個特性。
接下來我們去看 NULL 對索引的影響。MySQL 會對這個 NULL 列也加上索引,但是隻有 IS NULL 的查詢才會使用到索引。
之前我們去創建 t_do_not_use_null 張表結果的時候,對于這個two字段是給它加上了索引的,也就是 MySQL 庫它允許對這個 NULL 列加上索引。
我們通過 EXPLAIN 或者是 DESC,我們去檢驗一下 NULL 的列對象索引的影響。
使用 EXPLAIN 進行 two 字段的 NULL 的列進行索引分析
explain select * from t_do_not_use_null where two != 'e'\G
使用 EXPLAIN 分析的結果如下所示。

我們可以看到 EXPLAIN 或者是 DESC命令,它返回的信息如上圖所示,其中 key 字段是一個空,可能用到的索引是 idx_two,但是 key 字段是實際應用的索引,也就是為 NULL ,也就是我們當前的這樣一條查詢語句并不會使用到 two 這一列上建立的索引,這就是 NULL 對索引的一個影響。
隻有你去使用 IS NULL這樣的查詢,那才會使用到索引。比如把條件 two !='e' 改成 two is null 。
explain select * from t_do_not_use_null where two is null;
分析結果如下圖所示。

明顯的可以看到,可能使用到的索引是 idx_two,但是實際上使用的索引也是 idx_two。這就是因為這個 NULL 對索引的一個影響,大家需要明白,這也是 MySQL 中一個比較大的坑。當你對允許 NULL 的列加上索引,MySQL 很難對你的查詢進行優化。這也就是我們為什麼不建議你去使用 NULL 的一個重要的原因。
我們在之前創建表的時候,three 字段呢的是一個文獻索引,但是我們仍然插入了兩條為 NULL的記錄。
select * from t_do_not_use_null;

那麼S這個字段我們加上了唯一性索引,但是可以看到 id為 3 和 4 記錄,它們的這一列都是 NULL ,雖然 three 列定義了唯一性索引,但是也可以認為這一個唯一性失效了,也就是允許插入一條為NULL 或者是插入多條為 NULL 的記錄,也就是說唯一性索引對 NULL 是不生效的。這也是 MySQL 數據庫中 NULL 對索引的一個影響。
NULL 參與計算的時候,如果允許某個字段是 NULL,那麼這個字段的數據類型從程序的角度來說是不統一的。你很難把 NULL 跟整數或者字符串當作是同一個類型去處理。所以 MySQL 規定,NULL 值與任何的其他值進行計算,結果總是得到一個NULL,我們去驗證一下這一點。
驗證 1 2
select 1 2;
驗證的結果如下圖所示。

驗證 1 NULL
select 1 null;
驗證 1 NULL 結果如下圖所示。

如果我們讓 int NULL ,它會返回的就是 NULL。
驗證字符串與 NULL 拼接
select concat('mysql',null);
驗證字符串與 NULL 拼接結果如下圖所示。

可以看到這裡的結果也是 NULL,這就是允許 NULL的列造成數據不統一的一個結果。MySQL 就規定 NULL 值與任何其他類型的值去計算的時候,結果一定得到的是 NULL。
NULL 參與聚合也是一個非常重要的坑,也是一個非常容易引起問題的一個坑。我們去看一看參與聚合函數的 count 函數。
當某列存在 NULL 值時,在使用 count 查詢該列,就會出現數據丢失問題。如下 SQL 所示:
select count(*),count(three) from t_do_not_use_null;
查詢執行結果如下圖所示。

從上述查詢的結果來看,當使用的是 count(three) 查詢時,就丢失了兩條值為 NULL 的數據。
如果你對允許為 NULL 的列去使用到 count 聚合函數,它返回的實際上是不為 NULL 這一列的這一行記錄的數據。
所以對某一個列字段,你對它進行 count 聚合的時候,如果這一列值 NULL,那麼它所返回的結果是不包含字段為 NULL 的記錄。
聚合函數跟 NULL 去結合的時候,也會去産生一個不一樣的一個效果。如果在某列存在 NULL 值時,就使用 count(*) 進行數據統計。
總結:count(*) 會統計為 NULL 的行,為 count(列名) 不會統計此列為 NULL 值的行。
不知道大家有每有去考慮過,在 MySQL 中對數據進行排序。如果對某一列進行排序的時候,如果這一列會出現 NULL 值,那麼 MySQL 會如何進行處理呢?
我們先去嘗試對t_do_not_use_null 數據表進行一個排序,比如我們數據表中的這個 two 字段它是允許為 NULL,且它裡面存在着一些數據為 NULL,那我們就嘗試對 two 字段進行一個排序。
MySQL 排序分為正向排序和逆向排序兩種排序方式,正向排序排序使用 ASC,逆向排序使用 DESC。
使用 ASC 正向排序
select * from t_do_not_use_null order by two asc;
排序查詢結果如下所示。

從以上可以看出,除了 NULL 的列排到最前面之外,其他的記錄都會按照字符序列順序去排。
使用 ASC 逆向排序
select * from t_do_not_use_null order by two desc;
排序查詢結果如下所示。

反過來也是一樣,NULL 的列排在最後面。
注意:這隻是 MySQL 的一種規定或者是一種約定,而不是 NULL 的列更小。
因為 MySQl實在是不知道根據什麼去考慮、去排序,所以 MySQL 就給一個默認的規定,正序就排在最前,逆序就排在最後,隻是一種約定。
這裡給大家去講解了這個 NULL 它存在的一些問題。
第一個,使用特殊值去填充 NULL,例如:空字符串或者是數字0。這是最常見的一個替代方法。但是也需要注意,去結合你所處理的這個業務邏輯實現一個适配。比如對于這個字符類型來說,某一列是字符類型,那麼如果這一列确實沒有值,你可以去填充一個空字符串。對于數字類型也是一樣的,如果這一列不填充什麼内容,那麼數字零正好也可以去表達一下。
第二個,對于已經存在的數據表,填充特殊值到 NULL 這一列,再去修改表結構,也是去修改列的限制是 NOT NULL 的,這個是一個更好的做法。這樣它不僅僅規避了當前表中的NULL 的列,而且對表結構進行一個修改限制,這樣在将來也不會出現 NULL 的列,因為我們的表不允許為 NULL 的列出現了,你已經修改了這個列的限制是 NOT NULL ,,将來也就不會出現 NULL 問題。
最後我們就可以得出結論,那麼在任何場景下,你都不應該考慮去使用 NULL。也就是因為 NULL 存在各種各樣問題,或者說是一些特殊的特性。
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!