别踩坑!使用MySQL唯一索引请注意

背景

在程序设计中,我们往往需要确保数据的唯一性,比如在常见的注册模块,我们需要确保一个手机号只能注册为一个账号。这种情况下,我们的程序往往是第一道关卡,用户来注册之前,首先判断这个手机号是否已经注册,如果已经注册则返回错误信息,或直接去登录。但是我们不能确保同时有两个人使用同一个手机号注册到我们的系统中,因此这里就需要在更深的层次去确保手机号在系统的唯一性了。不同存储方案,解决方式不一样。对于常用的MySQL数据库,我们可以使用唯一索引的方式来作为我们的最后一道防线。

但是最近在使用数据库的唯一索引时,发现一个比较奇怪的现象。MySQL数据库,使用InnoDB存储引擎,创建了唯一索引时,在insert操作时,如果唯一索引上的字段有为NULL的情况,则可以无限插入。这有点匪夷所思,但是现实就是这么一个情况。现在就来具体分析这样的一个案例,来看看底层对于唯一索引是怎么设计的,来规避在数据库设计上犯错和踩坑。

案例

假设现在有一个用于保存用户信息的数据表user,是使用email注册的,当前使用email作为唯一索引,同时这一基本规则也被其他依赖系统作为设计数据模型的设计基础。假设现在设计这样一个user表:

CREATE TABLE `user` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',`email` varchar(32) NOT NULL DEFAULT '' COMMENT 'email',`name` varchar(11) DEFAULT '' COMMENT 'name',`age` int(11) DEFAULT NULL COMMENT 'age',PRIMARY KEY (`id`),UNIQUE KEY `uk-email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1@user.com来注册,执行insert语句,执行成功

INSERT INTO user (email,name,age) VALUES ('1@user.com','h1',18);

1@user.com再来注册,则再次执行,则报错。成功规避了用户多次创建导致系统产生脏数据问题。

Duplicate entry '1@user.com' for key 'uk-email'

从这里看,user表的设计是符合业务要求的,并没有出现同一个email出现多行的情况。随着业务发展,单单email注册的模式并不适合移动互联网时代,所以现在的要求在原有基础上增加了手机号的字段,并要求手机号也是唯一的。于是添加phone字段,并将原有唯一索引删除,为email和phone设置新的唯一索引。

ALTER TABLE `user` ADD COLUMN `phone` varchar(11) default NULL AFTER `age`;DROP INDEX `uk-email` ON `user`;ALTER TABLE `user` ADD UNIQUE KEY `uk-email-phone` (`email`,`phone`);

假设用户1再来用同样的email注册,可以注册成功:

INSERT INTO user (email,name,age,phone) VALUES (‘1@user.com’,‘h1’,18,NULL);

查询数据库数据,得到以下结果:
mysql-query-user-uk-index-1
有两个email为1@user.com的记录,他们的phone都是NULL,这怎么可能存在?!难道是MySQL出问题了?!不可能,我们再试另外一个数据

INSERT INTO user (email,name,age,phone) VALUES ('2@user.com','h2',18,'18812345678');

连续执行两次,第一次执行成功,第二次报错:

Duplicate entry ‘2@user.com-18812345678’ for key ‘uk-email-phone’

查询user结果集,得到
mysql-query-user-uk-index-2
从结果看这样MySQL的唯一索引也算是正常的啊,那这到底是怎么一回事呢?

原因探寻

业务中希望建立的唯一索引是email + phone的组合,但是由于phone一开始是没有数据的,所以新建字段时默认允许为NULL来兼容老数据。如果程序没有控制好,数据操作直接打到数据库,就产生了两条email为“1@user.com”且phone为NULL的数据,那么就会发生这种数据错乱的情况。

我从 MySQL 5.7官方文档 中找到了这个:

Unique Indexes

A UNIQUE index creates a constraint such that all values in the index must be distinct. An error occurs if you try to add a new row with a key value that matches an existing row. If you specify a prefix value for a column in a UNIQUE index, the column values must be unique within the prefix length. A UNIQUE index permits multiple NULL values for columns that can contain NULL.

官方的文档中明确说明在唯一索引中是允许存在多行值为NULL的数据存在的。

当然我们会认为这是MySQL的一个bug,其实早有人这么认为了,并给MySQL提出了这个问题https://bugs.mysql.com/bug.php?id=8173。但是MySQL的开发者并不认为这是一个bug,而是本身的一种设计。额,这么说,好像也说得过去。那这里就有一个问题了,我们知道索引是使用B+树来维护的,但是对于这种非唯一索引是怎么维护的?

带着这个问题,我觉得有两种可能:

(1)唯一索引时另外一种数据类型,正好把有值为NULL的字段过滤掉了,无需特殊处理。

(2)还是用的B+树索引,但是对于NULL的索引特殊处理了。

于是我对email=2@user.com且phone= 18812345678的数据执行了Explain执行计划

explain select * from user where `email` = '2@user.com' and `phone` = '18812345678';

mysql-query-user-uk-index-3
这个查询正好用到了唯一索引uk-email-phone,索引长度是134。

对email=1@user.com且phone为NULL的执行类似Explain执行计划

explain select * from user where `email` = '1@user.com' and `phone` is   NULL;

mysql-query-user-uk-index-4

对比上面两次不同数据的explain执行结果,可以看到其实都用了uk-email-phone的唯一索引,不同的是第一个type是const(通过一次索引就可以找到,用于primary key或unique index),第二个type是ref(非唯一性索引扫描),且rows为2。所以猜测这里极有可能是对NULL进行的特殊处理,唯一索引树还是用的和非NULL一样的唯一索引树。

源码分析

上面利用explain,测试结果是符合自己的猜测行为而已。也许只有源码中才能比较好的知道答案,基于此,在github上找到MySQL相关的源码(在此感谢DBA同学在唯一索引源码分析上的指点)。在这段源码https://github.com/mysql/mysql-server/blob/8e797a5d6eb3a87f16498edcb7261a75897babae/storage/innobase/row/row0ins.cc中,有一个方法 row_ins_scan_sec_index_for_duplicate(),这里会扫描唯一非聚簇索引树,来确定是否会发生唯一性的冲突。源码内有一段注释

/* If the secondary index is unique, but one of the fields in then_unique first fields is NULL, a unique key violation cannot occur,since we define NULL != NULL in this case */

在继续往下有一段这样的逻辑

	 cmp = cmp_dtuple_rec(entry, rec, index, offsets);if (cmp == 0 && !index->allow_duplicates) {if (row_ins_dupl_error_with_rec(rec, entry, index, offsets)) {err = DB_DUPLICATE_KEY;thr_get_trx(thr)->error_info = index;/* If the duplicate is on hidden FTS_DOC_ID,state so in the error log */if (index == index->table->fts_doc_id_index &&DICT_TF2_FLAG_IS_SET(index->table, DICT_TF2_FTS_HAS_DOC_ID)) {ib::error(ER_IB_MSG_958) << "Duplicate FTS_DOC_ID"" value on table "<< index->table->name;}goto end_scan;}} else {ut_a(cmp < 0 || index->allow_duplicates);goto end_scan;}

跳转到row_ins_dupl_error_with_rec()方法中有一段这样的逻辑

/* In a unique secondary index we allow equal key values if theycontain SQL NULLs */if (!index->is_clustered() && !index->nulls_equal) {for (i = 0; i < n_unique; i++) {if (dfield_is_null(dtuple_get_nth_field(entry, i))) {return (FALSE);}}}

在唯一索引中有字段为NULL的情况下,返回FALSE,代码中就没有抛出DB_DUPLICATE_KEY的异常了。所以从源码来看,这里实现了唯一索引允许为NULL的情况了,而且可以知道,这个唯一索引树和其他的二级索引基本上是没什么区别的。这也是前面explain时及时我们查询非唯一索引中另一个字段为空的记录,也还是用到了同样的索引和相同的索引长度。

反观来看,如果是我们在未知实现的情况下,要我们来设计,怎么实现允许有字段为NULL的唯一索引呢?是否还有比现有MySQL更好的方式来实现?

结论

所以其实MySQL在唯一索引中允许存在值为NULL的字段。NULL值在MySQL可以代表是任意值,并且在有字段值为NULL时,不会参与校验这个组合的唯一索引,所以可能插入业务上不允许重复的数据,导致脏数据。

因此在创建属于唯一索引的列时,最好指定字段值不能为空,在已有值为NULL的情况下,创建的字段不允许为空,且默认值为空字符。如果已经创建了默认值为NULL的字段,则先将其update为空字符,然后再修改为NOT NULL DEFAULT ‘’。如上述情况建表语句改为

CREATE TABLE `user` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',`email` varchar(32) NOT NULL DEFAULT '' COMMENT 'email',`name` varchar(11) DEFAULT '' COMMENT 'name',`age` int(11) DEFAULT NULL COMMENT 'age',`phone` varchar(11) NOT NULL DEFAULT '',PRIMARY KEY (`id`),UNIQUE KEY `uk-email-phone` (`email`,`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

并非所有数据库都是这样,SQL Server 2005及更老的版本,只允许有一个NULL值出现。从https://sqlite.org/faq.html#q26 了解到ANSI SQL-92标准:

A unique constraint is satisfied if and only if no two rows in a table have the same non-null values in the unique columns.(如果且仅当表中没有两行在唯一列中具有相同的非空值时,才满足唯一约束。)

除了MySQL之外,sqlLite、PostgreSQL、Oracle和FireBird也是允许唯一索引上存在多行为NULL。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/143398.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

mysql 唯一索引 死锁_MySQL死锁案例_唯一索引

近期在MySQL数据库中产生了死锁的情况&#xff0c;与通常的死锁不同&#xff0c;由于表中有唯一索引&#xff0c;所以加锁方式也比较有趣&#xff0c;本文将对于该例进行阐述(本文将对数据进行脱敏操作)&#xff1a; 问题描述:隔离级别&#xff1a;READ-COMMITTED 表结构&#…

聊聊mysql唯一索引的哪些坑,为什么还是产生重复数据?

前言 前段时间我踩过一个坑&#xff1a;在mysql8的一张innodb引擎的表中&#xff0c;加了唯一索引&#xff0c;但最后发现数据竟然还是重复了。 到底怎么回事呢&#xff1f; 本文通过一次踩坑经历&#xff0c;聊聊唯一索引&#xff0c;一些有意思的知识点。 1.还原问题现场 …

MySQL普通索引与唯一索引

MySQL普通索引与唯一索引 1. MySQL普通索引与唯一索引区别2. MySQL普通索引与唯一索引选择 1. MySQL普通索引与唯一索引区别 MySQL索引详解 MySQL索引分为很多种&#xff0c;常见的有主键索引与非主键索引&#xff0c;主键索引也就是我们常说的聚簇索引&#xff0c;主键索引叶…

MySQL | 普通索引和唯一索引,应该怎么选择?

在前面的基础篇文章中&#xff0c;我给你介绍过索引的基本概念&#xff0c;相信你已经了解了唯一索引和普通索引的区别。今天我们就继续来谈谈&#xff0c;在不同的业务场景下&#xff0c;应该选择普通索引&#xff0c;还是唯一索引&#xff1f; 假设你在维护一个市民系统&…

【MySQL】轻松学习 唯一索引

目录 &#x1f340;&#x1f340;&#x1f340;&#x1f340;&#x1f340; 引言&#xff1a;什么是索引 一、唯一索引概述 二、创建唯一索引 1.创建表时创建唯一索引 唯一索引与unique约束共存时 2、已存在的表上创建索引 2.1、使用create语句 2.2、使用alter table语句 引…

mysql索引(七)唯一索引

Mysql索引大概有五种类型&#xff1a; 普通索引(INDEX)&#xff1a;最基本的索引&#xff0c;没有任何限制 唯一索引(UNIQUE)&#xff1a;与"普通索引"类似&#xff0c;不同的就是&#xff1a;索引列的值必须唯一&#xff0c;但允许有空值。 主键索引(PRIMARY)&…

MySQL数据库-表索引-唯一索引

所谓唯一索引&#xff0c;就是在创建索引时&#xff0c;限制索引的字段值必须是唯一的。通过该类型的索引可以比普通索引更快的查询某条记录。 创建表时定义索引 语法&#xff1a; CREATE TABLE tablename( propName1 type1, propName2 type2, ... UNIQUE INDEX|KEY [indexn…

word文档图标变成白色

word文档图标变成白色&#xff0c;有可能是电脑上既有microsoft office&#xff0c;又安装又WPS&#xff08;WPS比较流氓&#xff0c;会篡改注册表中图标的路径&#xff0c;下图打个比方&#xff09; 图标路径被修改后&#xff0c;容易与office发生冲突&#xff0c;就会出现两种…

Word图表自动编号

1.选择【引用】菜单&#xff0c;然后点击【插入题注】。 2.弹出 【题注对话框】&#xff0c;点击【新建标签】。 3.弹出【新建标签】对话框&#xff0c;输入需要的【标签】&#xff0c;点击确定。如图所示&#xff0c;比如第一章的图&#xff0c;就输入 【图 1-】。 4.回到“题…

Java操作Word图表

一、POM <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.0</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml…

WORD图标显示为白色解决办法

&#xff08;操作过程幸得朋友相助&#xff0c;但我还是决定用写博客的方式把操作成功的过程写下来&#xff0c;以免以后再出现相关问题可以翻看自己的博客知快速获取解决办法&#xff0c;也能帮助到一些遇到类似状况的朋友。鄙人电脑小白&#xff0c;初入江湖&#xff0c;涉世…

计算机图标显示的是word,word图标不显示怎么办 设置显示word图标的方法

word是我们常用的办公软件&#xff0c;有时在使用时&#xff0c;图标会莫名其妙不显示&#xff0c;那么word图标不显示怎么办&#xff1f;下面小编带来设置显示word图标的方法&#xff0c;希望对大家有所帮助。 设置显示word图标的方法&#xff1a; 按键盘上的 Windows 徽标健 …

计算机word图标不显示,word图标不显示怎么办 设置图标显示的具体方法

word是我们常用的办公软件&#xff0c;有时在使用时&#xff0c;图标会莫名其妙不显示&#xff0c;那么word图标不显示怎么办&#xff1f;下面小编带来设置显示word图标的方法&#xff0c;希望对大家有所帮助。 设置显示word图标的方法&#xff1a; 按键盘上的Windows徽标健R键…

Word生成图表(柱状图、线形图等,并附带表格展示数值)

说明 Java poi实现生成图表并附带表格数据展示一、效果图与模板 1、模板 2、效果图 二、Word生成图标与报表工具类 1.工具类 代码如下&#xff1a; package com.ml.module.file.util.wordchart;import com.google.common.base.Strings; import com.google.common.collec…

word中实现图表的自动编号功能

工作中&#xff0c;经常需要对图表进行编辑和编号&#xff0c;有的时候会涉及图表的顺序调整、增加或删除&#xff0c;刚刚调整好的图表的编号就会被打乱&#xff0c;有的时候还会漏调&#xff0c;从而导致即使耗费了大量的时间&#xff0c;图表的序号依然会出现问题&#xff0…

使用word代码域自动为图表 标号

当写一个比较几万字文档时&#xff0c;文章中可能会有很多 图表&#xff0c;而且下文引用上文的图表&#xff0c;手动敲的比较麻烦。 word的域是一个比较复杂的一个功能&#xff0c;今天通过word的代码域&#xff0c;提供两种方法来解决 自动为图生成图注&#xff0c;并引用&am…

Word图标显示异常空白解决方案

问题描述 最近使用电脑时突然发现桌面的word文档图标变成了空白&#xff0c;之前也遇到过这个问题&#xff0c;但一时想不起来之前是怎么解决的&#xff08;只记得是跟注册表相关&#xff09;&#xff0c;遂上网查找了一番&#xff0c;经过一两个小时折腾后终于解决了问题&…

用手机打开word图表位置很乱_9个工作中经常用到的Word技巧,能大大提升你的工作效率...

不管是职场小白还是职场大神&#xff0c;Word基本是我们都需要用到办公软件&#xff0c;所以今天跟大家分享9个超级实用的word技巧&#xff0c;学会了能让你的工作效率快到起飞&#xff0c;保姆级教程&#xff0c;一起来看看吧&#xff01; 1、不滚动鼠标快速翻到100页 文档第1…

电脑系统安装Word图标变成白色怎么恢复?

Win11 Word图标变成白色怎么恢复&#xff1f;有一些朋友发现自己电脑上的doc格式的word文档图标是白色的&#xff0c;这是怎么回事呢&#xff1f;出现这种情况应该如何解决&#xff1f;大家肯定要先确保电脑上有安装Office软件&#xff0c;今天给朋友们讲讲具体的解决方法&…

电脑桌面图标全部变成Word图标?

最近有用户发现自己的电脑桌面图标全部变成了Word文档图标&#xff0c;这是什么情况&#xff1f;到底是哪里出现了问题&#xff1f;针对这一问题&#xff0c;下面就由小编给大家分享桌面图标全部变成Word图标的解决办法。 操作步骤&#xff1a; 1、双击打开桌面的“计算机”。 …