为什么建议MySQL列属性尽量NOT NULL

本文探讨了在MySQL数据库中使用NULL值对性能和查询优化的影响,包括存储空间、索引效率、查询处理等方面,以及如何优化涉及NULL值的列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文测试环境:MySQL5.7,Engine=InnoDB,charset=utf8。

先看一段官方的话:很多表都包含可为null(空值)的列,即使应用程序并不需要保存null也是如此,这是因为可为null是列的默认属性。通常情况下最好指定为Not null,除非真的需要存储null值。

如果查询中包含可为null的列,对MySQL来说更难优化,因为可为null的列使得索引、索引统计和值比较都更复杂。

可为null的列会使用更多的存储空间,在MySQL里也需要特殊处理。当可为null的列被索引时,每个索引记录需要一个额外的字节。在MyISAM里甚至还可能导致固定大小的索引(例如只有一个整数列的索引)变成可变大小的索引。

通常把可为null的列改为not null带来的性能提升比较小,所以调优时没有必要首先在现有schema中查找并修改掉这种情况,除非确定这会导致问题。但是如果计划在列上建索引,就应该尽量避免设计为可为null的列。

当然也有例外,值得一提的是,InnoDB使用单独的位(bit)存储null值,所以对于稀疏数据有很好的空间效率。但这一点不适用于MyISAM。关于这一点可以参考博文认真学习InnoDB的行格式

本地创建两个表,user 和user2,表字段一样,但是user中字段user_name不允许为null,user2中user_name default null,都为user_name添加上普通索引。

user表数据:

在这里插入图片描述

user2表数据:

在这里插入图片描述

① 在使用 not in、!=查询结果集时,字段值为null的列不会出现在结果中

如下所示:

select * from user2 where user_name !='admin'

查询结果如下(没有出现ID为2的那一列):

在这里插入图片描述

使用not in测试同上:

select * from user2 where user_name not in
(select user_name from user2 where user_id != 1)

② 在使用null作为条件判断时,应该使用is null或is not null ,而不要使用= 或者 !=

select * from user2 where user_name is null;
select * from user2 where user_name is NOT null;

③ 使用 concat 函数拼接时,首先要对各个字段进行非 NULL 判断,否则只要任何一个字段为空都会造成拼接的结果为 NULL。

如下所示,拼接最终结果为null:
在这里插入图片描述

null同样不能用于算术运算:

在这里插入图片描述

④ 在使用count(列名)进行统计时,null不会计入统计

select count(2) from user2;--2

select count(*) from user2;--2

select count(user_name) from user2;--1

如上所示,在使用count(列名)进行统计时,null不会计入统计

count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。


⑤ NULL 列需要更多的存储空间

一般需要一个额外的字节作为判断是否为 NULL 的标志位。如果你仔细观察 user 和user2表的 key_len,会发现 user2 比user 多了一个字节

explain select * from user where user_name ='admin'

在这里插入图片描述

explain select * from user2 where user_name ='admin';

在这里插入图片描述

key_len 的长度一般跟这三个因素有关,分别是数据类型,字符编码,是否为 NULL。

因此,user2比 user1 多出的这一个字节,用于作为判断是否为 NULL 的标志位了。

⑥ is null is not null可能用不到索引

如果某列可为null,那么当使用is null 或者 is not null进行判断的时候用不到索引。可能我们在很多地方看到过这句话,那么一定是这样吗?

① 单列索引测试

如下创建表testc2,单独为c1创建索引,c1 默认为null。

CREATE TABLE `testc2` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `c1` varchar(100) DEFAULT NULL,
  `c2` varchar(100) DEFAULT NULL,
  `c3` varchar(100) DEFAULT NULL,
  `c4` varchar(100) DEFAULT NULL,
  `c5` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `testc_c1_IDX` (`c1`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4

在这里插入图片描述

在没有null值存在的情况下进行验证,is not null没有用到索引 ,但是is null 用到了索引。

在这里插入图片描述
在这里插入图片描述

我们修改id=1的记录中c1为null,然后再进行测试。

update testc2  set c1 = null where id=1

在这里插入图片描述

如下两图所示 is not null没有用到索引,is null用到了索引。

在这里插入图片描述
在这里插入图片描述
针对上面测试结果,我们可以得到如下表格。

不存在null值存在null值
is nullUsing index conditionUsing index condition
is not nullUsing whereUsing index condition

出于好奇,我们将c1设置为not null,并填充了数据后再次进行测试,发现此时均用不到索引。

explain select *  from testc2 where c1 is not null ;

# Impossible WHERE
explain select *  from testc2 where c1 is  null ;

② 联合索引测试

CREATE TABLE `testc` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `c1` varchar(100) DEFAULT NULL,
  `c2` varchar(100) DEFAULT NULL,
  `c3` varchar(100) DEFAULT NULL,
  `c4` varchar(100) DEFAULT NULL,
  `c5` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `testc_c1_IDX` (`c1`,`c2`,`c3`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4

如上所示,我们创建联合索引,索引列可为null,但是此时没有数据为null。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
如上四种情况,均用到了索引。


我们将 id =1 的记录的c2设置为null,再进行上面验证:

update testc  set c2 =null where id=1;

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如上,四种情况,均用到了索引。


我们将 id =1 的记录的c1设置为null,再进行上面验证:

update testc  set c1 =null where id=1;

explain select *  from testc where c1 is null;

explain select *  from testc where c1 is not null;

explain select *  from testc where c1 ='b1' and c2 is  null;

explain select *  from testc where c1 ='b1' and c2 is not null;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,同样均使用到了索引 !

同样出于好奇(虽然没啥实际意义),我们将数据进行了填充并设置c1 not null ,然后进行测试,如下图所示,此时没有用到索引。
在这里插入图片描述

MySQL 中,NULL 值表示一个未知、不确定或者不可知的值。它并不等同于零(0)或空字符串(''),而是一种特殊的标记,用于表示数据的缺失或未定义状态。例如,在数据库表中,如果某一行的某个字段没有被赋值,则该字段的值通常会被设置为 NULL [^1]。 ### NULL 值的含义 - **未知值**:当数据尚未确定时,可以使用 NULL 表示该值尚未被提供。 - **不可知值**:某些情况下,即使尝试获取数据,也可能因为某些限制或条件而无法获得具体的值。 - **特殊含义**:对于某些特殊的数据类型,如 `TIMESTAMP` 和具有 `AUTO_INCREMENT` 属性NULL 值可能具有特殊的含义。例如,向 `TIMESTAMP` 插入 NULL 值时,系统会自动插入当前时间;向 `AUTO_INCREMENT` 插入 NULL 值时,系统会生成一个递增的整数值 [^4]。 ### NULL 值的使用方法 在使用 NULL 值时,需要注意以下几点: 1. **判断 NULL 值**:由于 NULL 值不能直接使用普通的比较运算符(如 `=`、`!=`)进行判断,必须使用 `IS NULL` 或 `IS NOT NULL` 来判断一个字段是否为 NULL。此外,`<=>` 是一种安全等于运算符,可以用于比较两个值是否相等,包括 NULL 值的情况 。 2. **NULL 与运算**:任何包含 NULL 值的表达式结果通常也会是 NULL。例如,`1 + NULL` 的结果是 NULL,`CONCAT('Invisible', NULL)` 的结果也是 NULL [^3]。 3. **统计行数**:在使用 `COUNT(column_name)` 函数时,它只会统计非 NULL 值的行数。因此,如果需要统计所有行(包括 NULL 值),应使用 `COUNT(*)` [^1]。 4. **索引与 NULL 值**:在某些类型的索引中,如空间索引,值必须为 NOT NULL。这意味着如果允许 NULL 值,则可能会影响索引的性能或有效性 [^1]。 5. **排序与 NULL 值**:在使用 `ORDER BY` 排序时,NULL 值的处理方式取决于排序顺序。在升序排序中,NULL 值会排在所有其他值之前;而在降序排序(DESC)中,NULL 值会排在所有其他值之后 [^1]。 ### 示例代码 以下是一些常见的 SQL 查询示例,展示了如何处理 NULL 值: ```sql -- 查询某个字段为 NULL 的记录 SELECT * FROM table_name WHERE column_name IS NULL; -- 查询某个字段不为 NULL 的记录 SELECT * FROM table_name WHERE column_name IS NOT NULL; -- 使用 COUNT(*) 统计所有行数 SELECT COUNT(*) FROM table_name; -- 使用 COUNT(column_name) 统计非 NULL 行数 SELECT COUNT(column_name) FROM table_name; -- 使用 COALESCE 函数将 NULL 值替换为默认值 SELECT COALESCE(column_name, 'default_value') FROM table_name; ``` ### 建议 - **避免使用 NULL 值**:在设计数据库时,尽量为字段设置合理的默认值,而不是使用 NULL 值。这可以减少因 NULL 值带来的复杂性和潜在问题 。 - **合理处理 NULL 值**:如果必须使用 NULL 值,则应确保在查询和业务逻辑中正确处理这些值,以避免出现意外结果 [^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值