最近遇到一个问题,我为一张表中创建了一个唯一键,并且键中字段为NULL
,最终导致了唯一约束失效。这里做下分析:
首先新建一张表,包含 work_no
,name
,age
三个字段:
DROP TABLE IF EXISTS t_emp;
CREATE TABLE t_emp(
id int(8) not null auto_increment,
work_no varchar(8) comment '工号',
name varchar(255) comment '姓名',
age int(3) comment '年龄',
primary key(id),
unique key(work_no,name,age)
)engine=InnoDB,charset=UTF8mb4,comment="员工表";
从创建语句中我们可以看到,我们为工号,姓名,年龄上添加了唯一约束,现在我们尝试插入数据:
insert into t_emp(work_no,name,age) values ('9527','张三',23);
insert into t_emp(work_no,name,age) values ('9527','张三',23);
我们在尝试插入第二条数据时会报错:
(1062, “Duplicate entry ‘9527-张三-23’ for key ‘work_no’”)
可见唯一键是生效了的,现在我们尝试将 age 为 null 再次尝试下:
insert into t_emp(work_no,name,age) values ('9527','张三',NULL);
insert into t_emp(work_no,name,age) values ('9527','张三',NULL);
可以看到当 age 为空时附加在该字段上的唯一约束失效了。
首先我们可以确认 null 值的出现破坏了唯一性,原本应该被唯一约束限制到的记录被插入到了表中。
如何确保 NULL
不破坏唯一性?
- 不允许列出现
NULL
值,即NOT NULL
- 给列增加
DEFUALT
值,注意DEFAULT
不能是NULL
所以需要调整建表语句:
DROP TABLE IF EXISTS t_emp;
CREATE TABLE t_emp(
id int(8) not null auto_increment,
work_no varchar(8) NOT NULL DEFAULT '' comment '工号',
name varchar(255) NOT NULL DEFAULT '' comment '姓名',
age int(3) NOT NULL DEFAULT 0 comment '年龄',
primary key(id),
unique key(work_no,name,age)
)engine=InnoDB,charset=UTF8mb4,comment="员工表";
简单来说就是不要在唯一约束上使用 NULL
,但是空字符串是可以的。
如果我们再继续寻找其根本原因的话,就要探究 NULL 在 MySQL 中的实现原理了。
深度探讨
关于NULL 值是否应该被唯一约束限制到的问题,早年有人向 MySQL 开发提出过问题。具体可以参考文档,可以看到开发者给出的解释是:
感兴趣的童鞋可以点进去看下,关于这个问题是否是一个BUG 的问题探讨的还是很激烈的。最终结果还是显而易见的,MySQL 保留了空值不受唯一约束的限制的这个特性。开发者通过调整空值为空字符串来处理该类异常问题。
NULL 与 NULL 不相等
还记得在 MySQL 中如何以字段值为 NULL 作为条件查询吗?
SELECT * FROM t_emp WHERE age IS NULL;
NULL 在 MySQL 中作为一个特殊的存在,我们无法使用平常使用的等值查询进行查询:
MySQL root@localhost:test> select null = null;
|| null = null ||
| <null> |
1 row in set
Time: 0.001s
MySQL root@localhost:test> SELECT NULL IS NULL;
|| NULL IS NULL ||
| 1 |
1 row in set
Time: 0.001s
比如我们有一个单列的唯一索引,既然实际会有空值的情况,如果唯一约束对空值也有起作用,就会导致仅有一行数据可以为空,这可能会和实际的业务需求相冲突的。所以通常MySQL的存储引擎的唯一索引对NULL值是不适用的。 这也就倒是联合唯一索引的情况下,只要某一列为空,就不会报唯一索引冲突。
或者说,我们一般认为在 MySQL 中 NULL 是一个没有被赋值的值,既然没有被赋值,那么它就有可能被赋值为任意值。这样就可以理解为两个任意值不相等,也就是两个 NULL 值是不相等的,因此也就可以在 唯一索引中单独存在了。
面试题:主键索引与唯一索引的区别
- 唯一索引的索引列允许空值,而主键索引的列不允许空值
- 唯一索引列存在空值时,唯一约束对空值时不生效的
- 主键索引可以被引用为外键,而唯一索引不能
- 一个表中最多只能创建一个主键索引,单可以创建多个唯一索引
主键索引
= 唯一索引
+ 唯一约束
+ 非空约束
唯一索引
= 唯一索引
+ 唯一约束
参考资料
MySQL: 唯一索引与NULL
Mysql 唯一索引的字段值
Bug #8173 unique index allows duplicates with null values