我們知道使用 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每日頭條,我们将持续为您更新最新资讯!