谨言慎行,友善交流
背景
有个业务场景依赖mysql的唯一索引进行防重,于是在涉及的字段上创建了一个联合唯一索引。某一天我们收到了业务告警,定位排查后发现是因为数据库里面有一条“重复的”数据。
1、问题
-- 创建一个测试表,在company和city上添加唯一索引
create table test_unique_key(
id int primary key,
company varchar(20) comment '公司',
city varchar(20) comment '城市',
unique index uk_company_city(company, city)
);
-- 成功插入第一条数据
insert into test_unique_key values(1, '公司1', null);
-- 成功插入第二条数据
insert into test_unique_key values(2, '公司1', null);
-- 查询结果
select * from test_unique_key;
id|company|city|
--+-------+----+
1|公司1 | |
2|公司1 | |
2、原因
问题都已经复现出来了,那直接查看官方文档吧。对于唯一索引的相关介绍如下图所示:
原来官方文档已经说明了,对于唯一索引是允许NULL重复出现的。其实之前也知道唯一索引的字段是允许为NULL的,但潜意识里我只想到单字段唯一索引,从没考虑到它对联合唯一索引的影响。
在我们的一般认知里('公司1', null)与('公司1', null)是属于重复数据的。尤其对于从Oracle迁移到mysql的用户来说,更应该是这样的(Oracle是可以检测出冲突的)。而在mysql里,NULL是与任何值(包括NULL)都不相等的。
3、解决
两个思路:
- 数据库侧:在相关字段上添加not null约束(调整之前一定要和业务侧确认好)
- 业务侧:去掉对数据库的冲突异常的依赖,通过其他方式进行防重
具体选择哪种方式需要结合修改成本和后续的维护成本去权衡了。
而对于我们个人来说,后续在设计MySQL表的时候,尽量先在字段上加上not null约束。在MySQL上,因为NULL而引起的经典问题太多了,没必要太纠结,懂得都懂。