首页 > 数据库 >MySQL的字段默认null对唯一索引的影响详解

MySQL的字段默认null对唯一索引的影响详解

时间:2022-10-05 13:02:21浏览次数:53  
标签:qq NULL aaa phone 索引 详解 MySQL null email

这篇文章主要为大家介绍了MySQL的字段默认null对唯一索引的影响详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

在日常业务开发中,会经常遇到需要保证唯一性的数据业务,如用户注册业务。一般注册业务中允许用户以手机号或email注册账号,且需要保证唯一,不允许重复注册。当用户输入手机号或email登录时,程序会判定输入信息的存在与否性,存在则走登录,不存在则走注册。而保证唯一性就不仅仅需要在程序端做判断,还需要MySQL的唯一索引去做最后一道防线。那么唯一索引在一些业务中使用,如果唯一索引字段中默认值设置为了null,会造成什么后果呢?

在阿里的《阿里巴巴Java开发手册》中关于MySQL-索引规范中写道: 【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须创建唯一索引。

说明:

不要以为唯一索引影响了insert速度,这个速度的损耗可以忽略不计,但提高查找的速度是明显的;

另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

看一下为何唯一索引为影响insert速度

在MySQL中,唯一索引树是一个非聚簇索引,每次插入数据时,都会在唯一索引树上进行遍历查找该插入值是否唯一,这也就是为什么会影响insert的速度,因为多一步遍历判断唯一性。

MySQL版本:在docker中启动一个mysql

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.18 |
+-----------+
1 row in set (0.00 sec)

假设只存在邮箱注册:

#建表语句
CREATE TABLE `user_1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`email` varchar(32) NOT NULL DEFAULT '' COMMENT '邮箱',
`name` varchar(11) DEFAULT '' COMMENT '名字',
PRIMARY KEY (`id`),
UNIQUE KEY `uk-email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert数据

#第一次插入:
insert into user(email,name) values('[email protected]','aaa');
Affected rows: 1, Time: 0.003000s
#再次插入同样的数据:
insert into user(email,name) values('[email protected]','aaa');
1062 - Duplicate entry '[email protected]' for key 'uk-email', Time: 0.005000s

此时对于唯一性来说是没问题的,可以保证业务的email的唯一性。假设随着业务的发展,此时需要增加手机号注册功能,那么表中就需要增加手机号字段,且需要保证手机号和邮箱的关联唯一性。

#建表语句,注意此时phone字段的默认值为null
CREATE TABLE `user_2` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`email` varchar(32) NOT NULL DEFAULT '' COMMENT '邮箱',
`phone` char(11) DEFAULT NULL COMMENT '手机号',
`name` varchar(11) DEFAULT '' COMMENT '名字',
PRIMARY KEY (`id`),
UNIQUE KEY `uk-email-phone` (`email`,`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert数据

insert into user_2(email,name) values('[email protected]','aaa');
Affected rows: 1, Time: 0.003000s
insert into user_2(email,name) values('[email protected]','aaa');
Affected rows: 1, Time: 0.003000s
insert into user_2(email,name) values('[email protected]','aaa');
Affected rows: 1, Time: 0.003000s
insert into user_2(email,phone,name) values('[email protected]','13333333333','bbb');
Affected rows: 1, Time: 0.003000s
insert into user_2(email,phone,name) values('[email protected]','13333333333','bbb');
1062 - Duplicate entry '[email protected]' for key 'uk-email-phone', Time: 0.002000s

此时会发现,不带phone值得前三条数据都能插入成功,带上邮箱和手机号的值却能正常判断唯一性

mysql> select * from user_2;
+----+------------+-------------+------+
| id | email | phone | name |
+----+------------+-------------+------+
| 1 | [email protected] | NULL | aaa |
| 2 | [email protected] | NULL | aaa |
| 3 | [email protected] | NULL | aaa |
| 4 | [email protected] | 13333333333 | bbb |
+----+------------+-------------+------+
4 rows in set (0.00 sec)

这时就需要牵扯到MySQL的唯一索引机制了:在MySQL官方文档中MySQL索引文档,描述到:

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.

解释一下:唯一索引创建一个约束,使得索引中的所有值都必须是不同的。如果尝试添加一个键值与现有行匹配的新行,则会发生错误。如果在唯一索引中为列指定前缀值,则列值在前缀长度内必须是唯一的。唯一索引允许包含空值的列有多个空值。

先看下explain执行计划:

mysql> explain select * from user_2 where email='[email protected]' and phone is NULL;
+----+-------------+--------+------------+------+----------------+----------------+---------+-------------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+----------------+----------------+---------+-------------+------+----------+-----------------------+
| 1 | SIMPLE | user_2 | NULL | ref | uk-email-phone | uk-email-phone | 132 | const,const | 3 | 100.00 | Using index condition |
+----+-------------+--------+------------+------+----------------+----------------+---------+-------------+------+----------+-----------------------+
1 row in set, 1 warning (0.01 sec)
mysql>
mysql> explain select * from user_2 where email='[email protected]' and phone='13333333333';
+----+-------------+--------+------------+-------+----------------+----------------+---------+-------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+----------------+----------------+---------+-------------+------+----------+-------+
| 1 | SIMPLE | user_2 | NULL | const | uk-email-phone | uk-email-phone | 132 | const,const | 1 | 100.00 | NULL |
+----+-------------+--------+------------+-------+----------------+----------------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

有没有发现一个有趣的现象,虽然两个sql语句都使用到了uk-email-phone唯一索引,但是 第一条sql的type为ref 第二条sql的type为const 我们知道,explain执行计划中,const一般是主键查询或者唯一索引查询是才会出现,而ref一般是使用普通索引时出现。所以,可以得出结论,MySQL在底层对唯一索引的null值做了特殊处理。

我们通过查看源码文件的1863行,有这么个注释:

Scans a unique non-clustered index at a given index entry to determine whether a uniqueness violation has occurred for the key value of the entry. Set shared locks on possible duplicate records

意思是扫描给定索引项处的唯一非聚集索引以确定条目的键值是否发生唯一性冲突。对可能重复的记录设置共享锁。

也就是说row_ins_scan_sec_index_for_duplicate()该方法就是处理唯一索引的,继续往下看,在1892行,有一串注释:

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

如果二级索引是唯一的,但是唯一索引的字段存在NULL,则不会发生唯一性冲突,在此代码中定义了NULL != NULL

继续往下走,在1996行,走到了row_ins_dupl_error_with_rec()函数,该函数在1825行。在该函数中有以下代码:

/* In a unique secondary index we allow equal key values if they
contain SQL NULLs
在唯一的二级索引中,如果包含sql NULL值
*/
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异常.

唯一索引重复插入之终极解决方案:给字段设置空字符串初始值,NOT NULL DEFAULT ''即可,不要用null值作为初始值。


标签:qq,NULL,aaa,phone,索引,详解,MySQL,null,email
From: https://blog.51cto.com/u_12148962/5732513

相关文章

  • Springboot2.6整合Swagger3时报错this.condition is null的解决方案
    错误Springboot2.6整合Swagger3时报错:this.conditionisnull:解决方案这个问题主要出现在SpringBoot2.6及以后,只要是SpringBoot2.6引入的新PathPatternParser导致的......
  • 传奇私服制作小地图的知识详解
    有的服务端小地图并不完整,或者有错。GM完全可以自已制作传奇小地图首先,需要准备3个工具1:Photoshop图像处理器2:乐都传奇地图编辑器3:Wil编辑工具制作方法1:打开乐都传奇编辑器......
  • 详解智能制造中的计算机视觉应用瓶颈问题
    编辑丨机器视觉课堂计算机视觉在智能制造工业检测中发挥着检测识别和定位分析的重要作用,为提高工业检测的检测速率和准确率以及智能自动化程度做出了巨大的贡献。然而计算机......
  • MYSQL学习笔记之索引
    (一)什么是索引??    索引(Index)是在数据库的字段上添加的,是为了提高查询的效率存在的一种机制。一张表的一个字段可以添加一个索引,当然,多个字段联合起来也可以添加索引。......
  • 详解线性回归-最小二乘法及其几何意义&最小二乘法-概率视角-高斯噪声-MLE【白板推导系
    $$\begin{gathered}D=\left{(x_{1},y_{1}),(x_{2},y_{2}),\cdots,(x_{N},y_{N})\right}\x_{i}\in\mathbb{R}^{p},y_{i}\in\mathbb{R},i=1,2,\cdots,N\X=\begin{pmat......
  • MySQL/MariaDB如何创建用户并限制指定才能IP访问?
    MySQL/MariaDB如何创建用户并限制指定才能IP访问?登入数据$mysql--versionmysqlVer15.1Distrib10.9.3-MariaDB,fordebian-linux-gnu(x86_64)usingreadline......
  • mysql常用命令
    MySQL常用命令退出mysql:exit查看mysql中有哪些数据库:showdatabases;注意:以分号结尾,分号是英文的分号mysql>showdatabases;+--------------------+|Database......
  • MySQL注入的几种绕过方式
        在平时进行MySQL注入的过程当中,有时会遇到过滤关键字的情况,这篇文章就介绍绕过几种过滤关键字的方式。    绕过过滤and和or    打开sqli的......
  • 全国地区 4级 5级 mysql 数据
    4级数据     https://files.cnblogs.com/files/wlphp/area_4.sql.zip?t=1664897005......
  • Mysql学习记录
    1.mysql内连接和外连接的区别连接语法:joinxxonxx(可以在一个语句中多次使用)内连接(innerjoin):取出两张表中匹配到的数据,匹配不到的不保留外连接(outerjoin):其中外连接又......