咱们晓得应用 MySQL 去存储数据,咱们须要先去建表,那么在表中去增加各个数据列,其实咱们须要给数据列指定一些属性,比如说咱们在列属性设置为容许,那就能够不搁置任何数据到这一列中。然而这会带来哪些麻烦或者是坑呢?那么带着这个问题,咱们来聊聊表属性设置为 NULL,你可能要面临很多麻烦。
为什么会有很多人用 NULL 呢?
第一点,NULL 它是一个默认行为,如果你不去被动指定这个列字段是 NOT NULL
,那么它就是 NULL
,这其实是容许 NULL
列最常见的起因了。很多刚开始学习应用 MySQL 的同学,没有显示的去指定当前列是 NOT NULL
,那么 MySQL
会主动的设置为当前列是容许 NULL
的。
第二点,有一个很重大的误区,就是很多初学者会认为 NULL 它代表的是空,也就是 Mysql 什么都不会存储,所以应用那能够节俭存储空间,然而实际上这是一个误区,那它是会占用存储空间的。
第三点,NULL
属性十分不便,SQL
语句或者代码不须要额定的填充或判断。这个说法看起来十分正当,咱们在操作数据时,如果某一列是空值,那么无论是 SQL
语句还是代码,咱们都不须要去思考它。
然而即便是有这么多起因,这么多人应用容许 NULL
的属性列,咱们仿佛还是会常常听到,那么应用 NULL
列并不好,那么咱们到底应该去怎么做抉择呢?也就是去应该听谁的呢?
咱们当然不能仅凭据说去认证哪种抉择会更好,也不能认为应用的人多就是对的。那么为了搞清楚这个问题,咱们能够看看官网对容许 NULL 列的一个形容。
看看 MySQL 官网对 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 列存在的问题
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
或者是不为 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 对索引的影响
接下来咱们去看 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
,那么这个字段的数据类型从程序的角度来说是不对立的。你很难把 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 参加聚合
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
值的行。
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 呢?
第一个,应用非凡值去填充 NULL
,例如:空字符串或者是数字 0。这是最常见的一个代替办法。然而也须要留神,去联合你所解决的这个业务逻辑实现一个适配。比方对于这个字符类型来说,某一列是字符类型,那么如果这一列的确没有值,你能够去填充一个空字符串。对于数字类型也是一样的,如果这一列不填充什么内容,那么数字零正好也能够去表白一下。
第二个,对于曾经存在的数据表,填充非凡值到 NULL
这一列,再去批改表构造,也是去批改列的限度是 NOT NULL
的,这个是一个更好的做法。这样它不仅仅躲避了以后表中的NULL
的列,而且对表构造进行一个批改限度,这样在未来也不会呈现 NULL
的列,因为咱们的表不容许为 NULL
的列呈现了,你曾经批改了这个列的限度是 NOT NULL
,,未来也就不会呈现 NULL
问题。
最初咱们就能够得出结论,那么在任何场景下,你都不应该思考去应用 NULL
。也就是因为 NULL
存在各种各样问题,或者说是一些非凡的个性。