【数据库】表字段设计时不推荐使用可空值(NULL)

【Mysql】数据库系列


文章目录


前言

在表设计字段时,不推荐使用可空值,推荐使用一个有意义的值去代替NULL,这样有利于代码的可读性和可维护性,并能从约束上增强业务数据的规范性。《高性能mysql》中给出的理由是:

Mysql难以优化引用可空列查询,它会使索引、索引统计和值更加复杂。可空列需要更多的存储空间,还需要mysql内部进行特殊处理。可空列被索引后,每条记录都需要一个额外的字节,还能导致MYisam 中固定大小的索引变成可变大小的索引。

下面举例子说明。


一、表和数据准备

创建表:student-01(name可以为NULL)、student-02(name不可以为NULL)

DDL:

sql 复制代码
-- `student-01` definition

CREATE TABLE `student-01` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `age` int DEFAULT '0' COMMENT '年龄',
  `name` varchar(50) DEFAULT NULL COMMENT '名字',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='学生表01';
sql 复制代码
-- `student-02` definition

CREATE TABLE `student-02` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `age` int DEFAULT '0' COMMENT '年龄',
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '名字',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='学生表';

数据:

sql 复制代码
INSERT INTO `student-01` (id, age, name) VALUES(1, 12, '小米1');
INSERT INTO `student-01` (id, age, name) VALUES(2, 23, '小rose1');
INSERT INTO `student-01` (id, age, name) VALUES(3, 12, '小jeck1');
INSERT INTO `student-01` (id, age, name) VALUES(4, 8, '小ming');
INSERT INTO `student-01` (id, age, name) VALUES(5, 11, '');
INSERT INTO `student-01` (id, age, name) VALUES(6, 46, 'NULL1');
INSERT INTO `student-01` (id, age, name) VALUES(7, 14, '小xu');
INSERT INTO `student-01` (id, age, name) VALUES(8, 11, NULL);
sql 复制代码
INSERT INTO `student-02` (id, age, name) VALUES(1, 12, '小米');
INSERT INTO `student-02` (id, age, name) VALUES(2, 23, '小rose');
INSERT INTO `student-02` (id, age, name) VALUES(3, 12, '小jeck');
INSERT INTO `student-02` (id, age, name) VALUES(4, 8, '');
INSERT INTO `student-02` (id, age, name) VALUES(5, 11, '');
INSERT INTO `student-02` (id, age, name) VALUES(6, 46, '小xiong');
INSERT INTO `student-02` (id, age, name) VALUES(7, 14, '小xu');

二、验证

1.NOT IN子查询在有NULL值的情况下返回永远为空结果

查询student-02中名字不存在student-01中的,推测的结果中应该有:小米、小rose、小jeck、小xiong,这几个只存在student-02。

sql 复制代码
select
	*
from
	`student-02` s
where
	s.name not in(
	select
		s2.name
	from
		`student-01` s2 )

运行结果:空,什么多没有。

2.使用!=去查询可空值字段时,数据中存在NULL,NULL记录查询不到

查询student-01中名字不等"小米1"的:

sql 复制代码
select * from `student-01` s where s.name  != '小米1'

输出结果:NULL值不包含在其中,id=8的记录没有包含进去。

3.如果在两个字段进行拼接:比如前缀+名字,字段为空会造成拼接的结果为NULL

将student-01表中name值加上"kexuexiong-"前缀:

sql 复制代码
select  s.id,CONCAT("kexuexiong-",s.name ) as name from `student-01` s

输出结果:

4.如果有 Null column 存在的情况下,count(Null column),null 值不会参与统计

统计名字有多少个:

sql 复制代码
select count(s.name ) from `student-01` s 

输出结果:

5. Null 字段的判断方式

使用!=NULL

sql 复制代码
select * from `student-01` s where s.name != null

输出结果;

使用=NULL

sql 复制代码
select * from `student-01` s where s.name = null

上面这两种方式是新手经常犯的错误,正确的方式应该是:

使用is not null

sql 复制代码
select * from `student-01` s where s.name is not null

使用is null

sql 复制代码
select * from `student-01` s where s.name is null

6.可空字段索引,会增加额外的空间

给student-01的name增加索引:

sql 复制代码
CREATE INDEX student_01_name_IDX USING BTREE ON `student-01` (name);

给student-02的name增加索引:

sql 复制代码
CREATE INDEX student_02_name_IDX USING BTREE ON `student-02` (name);

使用explain分析sql:

sql 复制代码
explain select * from `student-01` s where s.name = '小米1'
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE s ref student_01_name_IDX student_01_name_IDX 203 const 1 100.0
sql 复制代码
explain select * from `student-02` s where s.name = '小米'
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE s ref student_02_name_IDX student_02_name_IDX 202 const 1 100.0

两条查询语句都走name索引,但是key_len(索引字段的长度)不同。可空值得student-01(203)中比不可空值的student-02(202)多了1,两者类型都为varchar(50),编码集:utf8mb4。

key_len 的计算规则和三个因素有关:数据类型、字符编码、是否为 NULL

  • key_len 202 == 50*4(utf8 4字节) + 2 (存储 varchar 变长字符长度 2字节,定长字段无需额外的字节)

  • key_len 203== 20*4(utf8mb4 4字节) + 1 (是否为 Null 的标识) + 2 (存储 varchar 变长字符长度 2字节,定长字段无需额外的字节)


总结

综合以上的案列分析,不建议字段设计时使用可空值。推荐使用一个有意义的值去代替NULL,这样有利于代码的可读性和可维护性,并能从约束上增强业务数据的规范性。

相关推荐
随风飘的云9 小时前
MySQL的慢查询优化解决思路
数据库
IvorySQL13 小时前
PostgreSQL 技术日报 (3月7日)|生态更新与内核性能讨论
数据库·postgresql·开源
赵渝强老师14 小时前
【赵渝强老师】金仓数据库的数据文件
数据库·国产数据库·kingbase·金仓数据库
随逸17717 小时前
《Milvus向量数据库从入门到实战,手把手搭建语义检索系统》
数据库
神秘的猪头18 小时前
🚀 React 开发者进阶:RAG 核心——手把手带你玩转 Milvus 向量数据库
数据库·后端·llm
IvorySQL1 天前
PostgreSQL 技术日报 (3月6日)|为什么 Ctrl-C 在 psql 里让人不安?
数据库·postgresql·开源
NineData2 天前
数据库管理工具NineData,一年进化成为数万+开发者的首选数据库工具?
运维·数据结构·数据库
IvorySQL2 天前
PostgreSQL 技术日报 (3月5日)|规划器控制力升级,内核能力再进阶
数据库·postgresql·开源
数据组小组2 天前
免费数据库管理工具深度横评:NineData 社区版、Bytebase 社区版、Archery,2026 年开发者该选哪个?
数据库·测试·数据库管理工具·数据复制·迁移工具·ninedata社区版·naivicat平替