MySQL唯一索引与Null之间的“羁绊”
前言
前几天项目组的产品经理找我,说发现了几条数据不一致的情况。数据不一致的地方是两个接口,一个是我写的,另一个是一位离职的同事大佬写的。已解决,遂发此文记录。
问题排查
对于我来讲,感觉在项目的开发中,遇到接口错误还是比较好解决的事情,但是遇到这种数据不一致的问题,考虑的方面就比较多了。回归到这个问题,出现数据不一致的情况,首先要缩短时间范围,跟办案一样,先查清楚哪一天的数据不一致,其它时间的数据是否一致,如果其它时间的数据一致,那么大概率可能不是接口的问题;如果仅有几条数据不一致,那么就要考虑是不是数据库表造成的问题了。
在页面上哐哐就是一顿点,看出来几个地区的数据在某一天不一致,虽然定位到了是数据库表的问题,但还是去瞄了几眼前同事的代码,还是挺复杂的,没发现什么问题,遂转移去看实际造成错误的数据库。好几个地区的数据会多几十甚至几百条,我也创建了唯一索引,思考了挺长时间。之后把问题解决了,这里记录一下这个问题。
问题解决
我以举例子的方式来描述这个问题,和往常一样,先记录下前置知识:
众所周知,在数据库表中,唯一索引的作用是用来确保在索引中的每一行数据都是唯一的。这就意味着对于唯一索引所包含的字段组合,任何两行的数据在这几个字段上的值不能完全相同。
接着单看数据库查不到具体情况,去查odps,我遇到的具体情况:
比如我们有一个表,就叫ddd
,给它创建一个结构:
CREATE TABLE ddd (
a INT,
b INT,
c INT,
);
在这个表中,我设置了一个唯一索引,包含了字段a
、b
和c
。所以,对于这几个字段,肯定是不会重复的。但如果其中一个字段为null呢,会不会遇到这种情况:
a | b | c |
---|---|---|
1 | 2 | NULL |
1 | 2 | NULL |
1 | 3 | 5 |
答案是会遇到的,把出现数据不一致的那天的一个地区的数据全部查出来,一条一条对比发现,两个接口查询的两个数据库表中的汇总数据不一致。原因就是因为这个,两个数据库表中加的索引不一样,在odps处理t-3到t-10的数据的时候,由于odps表是增量的,一个字段为空,还被设置了索引,导致odps离线同步到数据库表中数据是上面这样的。
根据唯一索引的定义,这两种情况会被视为不同的记录:
(a=1, b=2, c=NULL)
(a=1, b=2, c=NULL)
即使c
字段为空(即NULL
),这些记录仍然可以存在于表中,因为数据库系统认为每个NULL
值都是唯一的,即使它们看起来相同。
- 第一行和第二行的
a
和b
字段都相同,且c
字段为NULL
。由于NULL
值被视为不同的,这两行不会违反唯一性约束。 - 第三行由于
b
字段不同,也不会违反唯一性约束。
很久之前做毕设的时候遇到过这样的问题,解决了之后,没想到这次又遇到了一次,再回忆一下这个知识点!
唯一索引与NULL值
在我们经常接触到的关系型数据库中,唯一索引对NULL
值的处理方式有一些特殊规则:
-
多个
NULL
值被视为不同的值:尽管唯一索引要求字段组合是唯一的,但大多数数据库都将NULL
视为一个特殊的标记,表示“缺失值”,而不是具体的值。所以,在唯一索引中,多个NULL
值不会违反唯一性约束。 -
具体实现差异:不同数据库系统对
NULL
值在唯一索引中的处理可能略有不同,但大多数主流数据库系统允许在同一唯一索引列中存在多个NULL
值。
解决方案
解决方案比较简单,我问了同事,同事说那两天有点问题,导致部分地区的那一列为null值,已经修好了代码,后面不会再出现这种情况了。这是第一种解决方案,在项目代码层面解决问题!
第二种是使用部分唯一索引,把咱们这个例子中的c这一列从唯一索引中去掉就行了(如果无关紧要,不参与查询或者排序的话,不然删除索引可能会降低查询性能)。
还有第三种方法就是使用默认值代替null值,我在odps语句中也做处理了,如果为null的话就让它为" ",这样数据在离线同步到数据库表中的时候也不会因为这个数据有问题了(可以使用上篇文章介绍的COALESCE(c, 0)这个函数来做)。
文章小结
在大多数数据库系统中,唯一索引允许同一组字段组合中有多个NULL
值,因为每个NULL
被视为不同的。这块知识点相信大佬们肯定都了解,这次遇到了这个问题,索性记录一下,同时也希望这篇文章能给屏幕前的你们带来帮助。
本篇文章到这里就结束了,后续会继续分享相关的知识,感谢各位小伙伴们的支持!