MySQL重大Bug!自增主键竟然不是连续递增(下)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: MySQL重大Bug!自增主键竟然不是连续递增

自增锁的养成计划

所以自增id的锁并非事务锁,而是每次申请完就马上释放,其它事务可以再申请。其实,在MySQL 5.1版本之前,并不是这样的。


MySQL 5.0时,自增锁的范围是语句级别:若一个语句申请了一个表自增锁,该锁会等语句执行结束以后才释放。显然,这样影响并发度


MySQL 5.1.22版本引入了一个新策略,新增参数innodb_autoinc_lock_mode,默认值1。该参数的值为0时,表示采用5.0的策略,设置为1时:


  • 普通insert语句
    申请后,马上释放;
  • 类似insert … select 这样的批量插入语句
    等语句结束后,才释放


设置为2时,所有的申请自增主键的动作都是申请后就释放锁。

为什么默认设置下的insert … select 偏偏要使用语句级锁?为什么该参数默认值不是2?


为了数据的一致性

看个案例:批量插入数据的自增锁

image.png

若session2申请了自增值后,马上释放自增锁,则可能发生:


  • session2先插入了两个记录,(1,1,1)、(2,2,2)
  • 然后,session1来申请自增id得到id=3,插入(3,5,5)
  • session2继续执行,插入两条记录(4,3,3)、 (5,4,4)


这好像也没关系吧,毕竟session 2语义本身就没有要求t2的所有行数据都和session1相同。

从数据逻辑角度看是对的。但若此时binlog_format=statement,binlog会怎么记录呢?

先看看 MySQL 此时的告警:

mysql> insert into t2(c,d) select c,d from t;
Query OK, 4 rows affected, 1 warning (0.01 sec)
Records: 4  Duplicates: 0  Warnings: 1
mysql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                                                                                                          |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1592 | Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statements writing to a table with an auto-increment column after selecting from another table are unsafe because the order in which rows are retrieved determines what (if any) rows will be written. This order cannot be predicted and may differ on master and the slave. |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

由于两个session同时执行插入数据命令,所以binlog里对表t2的更新日志只有两种情况:要么先记session1,要么先记session2。

但无论哪种,这个binlog拿去从库执行或用来恢复临时实例,备库和临时实例里面,session2这个语句执行出来,生成的结果里,id都是连续的。 此时该库就发生了数据不一致。


因为原库session2的insert语句,生成的id不连续。这个不连续的id,用statement格式的binlog来串行执行,是执行不出来的。

要解决该问题,有如下方案:


  1. 让原库的批量插入数据语句,固定生成连续id值

所以,自增锁直到语句执行结束才释放,就是为了达此目的

  1. 在binlog里把插入数据的操作都如实记录进来,到备库执行时,不依赖自增主键去生成

其实就是innodb_autoinc_lock_mode=2,同时binlog_format=row。


所以生产上有insert … select这种批量插入场景时,从并发插入的性能考虑,推荐设置:innodb_autoinc_lock_mode=2 && binlog_format=row,既能提升并发性,又不会出现数据一致性问题。


这里的“批量插入数据”,包含如下语句类型:

  • insert … select
  • replace … select
  • load data


在普通insert语句包含多个value值的场景,即使innodb_autoinc_lock_mode=1,也不会等语句执行完成才释放锁。因为这类语句在申请自增id时,可以精确计算出需要多少个id,然后一次性申请,申请完成后锁即可释放。


即批量插入数据的语句,之所以需要这么设置,是因为“不知道要预先申请多少个id”。

既然不知道要申请多少个自增id,那么最简单的就是需要一个时申请一个。但若一个select … insert要插入10万行数据,就要申请10w次,速度慢还影响并发插入性能。


因此,对于批量插入数据语句,MySQL提供了批量申请自增id的策略:

  1. 语句执行过程中,第一次申请自增id,会分配1个
  2. 1个用完以后,这个语句第二次申请自增id,会分配2个
  3. 2个用完以后,还是这个语句,第三次申请自增id,会分配4个


依此类推,同一个语句去申请自增id,每次申请到的自增id个数都是上一次的两倍。

看案例:

image.png

mysql> create table t2 like t;
mysql> insert into t2(c,d) select c,d from t;
Query OK, 4 rows affected, 1 warning (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 1
mysql> insert into t2 values(null, 5,5);
Query OK, 1 row affected (0.00 sec)
mysql> select * from t2;
+----+------+------+
| id | c    | d    |
+----+------+------+
|  1 |    1 |    1 |
|  2 |    2 |    2 |
|  3 |    3 |    3 |
|  4 |    4 |    4 |
|  8 |    5 |    5 |
+----+------+------+
5 rows in set (0.00 sec)

insert…select实际上往t2中插入4行数据。但这四行数据是分三次申请的自增id,第一次申请到id=1,第二次id=2和id=3, 第三次id=4到id=7。

由于该语句实际只用上了4个id,所以id=5到id=7就被浪费了。之后,再执行

insert into t2 values(null, 5,5)

实际上插入的数据是(8,5,5)。这是主键自增id不连续的三大原因。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
17天前
|
关系型数据库 MySQL
MYSQL:约束(主键约束)
MYSQL:约束(主键约束)
|
6天前
|
SQL Java 数据库连接
2万字实操案例之在Springboot框架下基于注解用Mybatis开发实现基础操作MySQL之预编译SQL主键返回增删改查
2万字实操案例之在Springboot框架下基于注解用Mybatis开发实现基础操作MySQL之预编译SQL主键返回增删改查
16 2
|
21天前
|
关系型数据库 MySQL 数据库
MySQL 8.0 新特性之不可见主键
【6月更文挑战第9天】MySQL 8.0 引入了不可见主键特性,提供更灵活的数据库管理方式。不可见主键能减少业务逻辑干扰,提高数据安全性和隐私,同时在某些场景下更适用。示例展示了如何创建和使用不可见主键,但需要注意它可能带来的理解和调试难题。此特性增加了设计和管理数据库的选项,适用于对数据隐私有高要求的场景。随着技术发展,不断学习和探索新特性将提升数据库性能和功能。
39 9
|
16天前
|
SQL 关系型数据库 MySQL
字节面试:MySQL自增ID用完会怎样?
字节面试:MySQL自增ID用完会怎样?
25 0
字节面试:MySQL自增ID用完会怎样?
|
24天前
|
存储 SQL 关系型数据库
MySQL数据库——SQL优化(1/3)-介绍、插入数据、主键优化
MySQL数据库——SQL优化(1/3)-介绍、插入数据、主键优化
236 1
|
2月前
|
存储 关系型数据库 MySQL
在MySQL中, 自增主键和UUID作为主键有什么区别?
自增主键和UUID在MySQL中各有优缺点,选择哪种方式作为主键取决于具体的应用场景和需求。例如,在需要高性能插入和查询的场景下,自增主键可能更合适;而在需要保证主键全局唯一性和不可预测性的场景下,UUID可能更合适。
23 0
|
3天前
|
存储 关系型数据库 MySQL
|
3天前
|
存储 SQL 关系型数据库
|
4天前
|
存储 关系型数据库 MySQL
|
4天前
|
SQL 运维 关系型数据库