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

目录
  • 正文
    • 看一下为何唯一索引为影响insert速度
      • MySQL版本:在docker中启动一个mysql
      • 假设只存在邮箱注册:
      • insert数据
    • 经验

正文

在日常业务开发中,会经常遇到需要保证唯一性的数据业务,如用户注册业务。一般注册业务中允许用户以手机号或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('aaa@qq.com','aaa');
Affected rows: 1, Time: 0.003000s
#再次插入同样的数据:
insert into user(email,name) values('aaa@qq.com','aaa');
1062 - Duplicate entry 'aaa@qq.com' 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('aaa@qq.com','aaa');
Affected rows: 1, Time: 0.003000s
insert into user_2(email,name) values('aaa@qq.com','aaa');
Affected rows: 1, Time: 0.003000s
insert into user_2(email,name) values('aaa@qq.com','aaa');
Affected rows: 1, Time: 0.003000s
insert into user_2(email,phone,name) values('bbb@qq.com','13333333333','bbb');
Affected rows: 1, Time: 0.003000s
insert into user_2(email,phone,name) values('bbb@qq.com','13333333333','bbb');
1062 - Duplicate entry 'bbb@qq.com-13333333333' for key 'uk-email-phone', Time: 0.002000s

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

mysql> select * from user_2;
+----+------------+-------------+------+
| id | email      | phone       | name |
+----+------------+-------------+------+
|  1 | aaa@qq.com | NULL        | aaa  |
|  2 | aaa@qq.com | NULL        | aaa  |
|  3 | aaa@qq.com | NULL        | aaa  |
|  4 | bbb@qq.com | 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='aaa@qq.com' 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='bbb@qq.com' 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值作为初始值。

以上就是详解MySQL的字段默认null对唯一索引的影响的详细内容,更多关于MySQL字段默认null唯一索引的资料请关注我们其它相关文章!

(0)

相关推荐

  • Mysql普通索引与唯一索引的选择详析

    假设一个用户管理系统,每个人注册都有一个唯一的手机号,而且业务代码已经保证了不会写入两个重复的手机号.如果用户管理系统需要按照手机号查姓名,就会执行类似这样的 SQL 语句: select name from users where mobile = '15202124529'; 通常会考虑在 mobile 字段上建索引.由于手机号字段相对较大,通常基本不会把手机号当做主键,那么现在就有两个选择: 1.  给 id_card 字段创建唯一索引 2.  创建一个普通索引 如果业务代码已经保证了不会

  • MySQL为何不建议使用默认值为null列

    通常能听到的答案是使用了NULL值的列将会使索引失效,但是如果实际测试过一下,你就知道IS NULL会使用索引.所以上述说法有漏洞. 着急的人拉到最下边看结论 Preface Null is a special constraint of columns. The columns in table will be added null constrain if you do not define the column with "not null" key words explicit

  • 如何利用MySQL添加联合唯一索引

    目录 联合唯一索引 扩展延伸: 附:mysql中如何用命令创建联合索引 总结 联合唯一索引 项目中需要用到联合唯一索引: 例如:有以下需求:每个人每一天只有可能产生一条记录:处了程序约定之外,数据库本身也可以设定: 例如:t_aa 表中有aa,bb两个字段,如果不希望有2条一模一样的记录(即:aa字段的值可以重复: bb字段的值也可以重复,但是一条记录(aa,bb)组合值不允许重复),需要给 t_aa 表添加多个字段的联合唯一索引: alter table t_aa add unique ind

  • MySQL唯一索引和普通索引选哪个?

    想象这样一个场景,在设计一张用户表时,每人的身份证号是唯一的,需要搜索.但由于身份证号字段较大,不好将其作为主键.在业务代码已经保证插入身份证唯一的情况下,可以选择建立唯一索引和普通索引,这时该如何选择呢?接下来,将从查询和更新的执行过程进行分析. 查询过程 假设 k 是表 t 上的索引,在搜索 select id from t where k=5 时,会先从 k 这棵 B+ 的树根开始,按层搜索叶子节点,找到 k=5 的数据页,然后在数据页内容进行二分法定位. 对于普通索引,找到 k=5 的记

  • 解决JPA save()方法null值覆盖掉mysql预设的默认值问题

    目录 JPA save()方法null值覆盖掉mysql预设的默认值 覆盖原因 解决办法 data jpa动态插入(null为sql默认值,utime自动更新 ) JPA save()方法null值覆盖掉mysql预设的默认值 覆盖原因 save()方法在没有参数传进去的时候默认是null值,而mysql表中该字段设置为可以为null值,这时虽然我们设置了默认值,可null值还是会把默认值覆盖掉. 解决办法 将该字段设置为不允许null值即可,这样null值就会被替换为默认值. data jpa

  • MySQL 普通索引和唯一索引的区别详解

    1 概念区分 普通索引和唯一索引 普通索引可重复,唯一索引和主键一样不能重复. 唯一索引可作为数据的一个合法验证手段,例如学生表的身份证号码字段,我们人为规定该字段不得重复,那么就使用唯一索引.(一般设置学号字段为主键) 主键和唯一索引 主键保证数据库里面的每一行都是唯一的,比如身份证,学号等,在表中要求唯一,不重复.唯一索引的作用跟主键的作用一样. 不同的是,在一张表里面只能有一个主键,主键不能为空,唯一索引可以有多个,唯一索引可以有一条记录为空,即保证跟别人不一样就行. 比如学生表,在学校里

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

    目录 正文 看一下为何唯一索引为影响insert速度 MySQL版本:在docker中启动一个mysql 假设只存在邮箱注册: insert数据 经验 正文 在日常业务开发中,会经常遇到需要保证唯一性的数据业务,如用户注册业务.一般注册业务中允许用户以手机号或email注册账号,且需要保证唯一,不允许重复注册.当用户输入手机号或email登录时,程序会判定输入信息的存在与否性,存在则走登录,不存在则走注册.而保证唯一性就不仅仅需要在程序端做判断,还需要MySQL的唯一索引去做最后一道防线.那么唯

  • 详解mysql权限和索引

    mysql权限和索引 mysql的最高用户是root, 我们可以在数据库中创建用户,语句为CREATE USER 用户名 IDENTIFIED BY '密码',也可以执行CREATE USER 用户名 语句来创建用户,不过此用户没有密码,可以将用户登录后进行密码设置:删除用户语句为DROP USER 用户:更改用户名的语句为RENAME USER 老用户名 to 新用户名: 修改密码语句为set password=password('密码'): 高级用户修改别的用户密码的语句为SET PASSW

  • 详解mysql不等于null和等于null的写法

    1.表结构 2.表数据 3.查询teacher_name字段不能等于空并且也不能等于空字符 SELECT * FROM sys_teacher WHERE teacher_name IS NOT NULL AND teacher_name <>'' 查询结果: 4.查询teacher_name字段等于null或等于空字符 SELECT * FROM sys_teacher WHERE teacher_name = '' OR teacher_name IS NULL 查询结果: 到此这篇关于详

  • 详解mysql三值逻辑与NULL

    什么是NULL NULL 用于表示缺失的值或遗漏的未知数据,不是某种具体类型的值.数据表中的 NULL 值表示该值所处的字段为空,值为 NULL 的字段没有值,尤其要明白的是:NULL 值与 0 或者空字符串是不同的. 两种 NULL 这种说法大家可能会觉得很奇怪,因为 SQL 里只存在一种 NULL .然而在讨论 NULL 时,我们一般都会将它分成两种类型来思考:"未知"(unknown)和"不适用"(not applicable,inapp licable).

  • 详解MySQL中数据类型和字段类型

    目录 1. mysql的数据类型 (1)数值型 (2)字符(串)型 (3)日期和时间型 (4)null值 2. mysql的列(字段)类型 2.1数值列类型 2.2字符串列类型 2.3日期时间列类型 1. mysql的数据类型 在mysql中有如下几种数据类型: (1)数值型 数值是诸如32 或153.4 这样的值.mysql 支持科学表示法,科学表示法由整数或浮点数后跟“e”或“e”.一个符号(“+”或“-”)和一个整数指数来表示.1.24e+12 和23.47e-1 都是合法的科学表示法表示

  • 详解MySQL like如何查询包含'%'的字段(ESCAPE用法)

    在SQl like语句中,比如 SELECT * FROM user WHERE username LIKE '%luchi%' SELECT * FROM user WHERE username LIKE '_luchi_', % 作为通配符通配多个 _ 作为通配符通配一个 但当like 所要查询的字段中含有 % ,我们如何去查: 这个时候就需要指明 字段中的那个'%' 不作为通配符: 这里就需要用到 ESCAPE 转义 测试: 这里我们使用这张表 转义前: SELECT * FROM use

  • 详解MySQL 外键约束

    官方文档: https://dev.mysql.com/doc/refman/5.7/en/create-table-foreign-keys.html 1.外键作用: MySQL通过外键约束来保证表与表之间的数据的完整性和准确性. 2.外键的使用条件 两个表必须是InnoDB表,MyISAM表暂时不支持外键(据说以后的版本有可能支持,但至少目前不支持) 外键列必须建立了索引,MySQL 4.1.2以后的版本在建立外键时会自动创建索引,但如果在较早的版本则需要显示建立: 外键关系的两个表的列必须

  • 详解mysql DML语句的使用

    前言: 在上篇文章中,主要为大家介绍的是DDL语句的用法,可能细心的同学已经发现了.本篇文章将主要聚焦于DML语句,为大家讲解表数据相关操作. 这里说明下DDL与DML语句的分类,可能有的同学还不太清楚. DDL(Data Definition Language):数据定义语言,用于创建.删除.修改.库或表结构,对数据库或表的结构操作.常见的有create,alter,drop等. DML(Data Manipulation Language):数据操纵语言,主要对表记录进行更新(增.删.改).

  • 详解MySQL分区表

    前言: 分区是一种表的设计模式,通俗地讲表分区是将一大表,根据条件分割成若干个小表.但是对于应用程序来讲,分区的表和没有分区的表是一样的.换句话来讲,分区对于应用是透明的,只是数据库对于数据的重新整理.本篇文章给大家带来的内容是关于MySQL中分区表的介绍及使用场景,有需要的朋友可以参考一下,希望对你有所帮助. 1.分区的目的及分区类型 MySQL在创建表的时候可以通过使用PARTITION BY子句定义每个分区存放的数据.在执行查询的时候,优化器根据分区定义过滤那些没有我们需要的数据的分区,这

  • 详解MySQL中的数据类型和schema优化

    最近在学习MySQL优化方面的知识.本文就数据类型和schema方面的优化进行介绍. 1. 选择优化的数据类型 MySQL支持的数据类型有很多,而如何选择出正确的数据类型,对于性能是至关重要的.以下几个原则能够帮助确定数据类型: 更小的通常更好 应尽可能使用可以正确存储数据的最小数据类型,够用就好.这样将占用更少的磁盘.内存和缓存,而在处理时也会耗时更少. 简单就好 当两种数据类型都能胜任一个字段的存储工作时,选择简单的那一方,往往是最好的选择.例如整型和字符串,由于整型的操作代价要小于字符,所

随机推荐