掘金 后端 ( ) • 2024-04-09 09:06

写在文章开头

大部分文章都说UUID的随机性会的插入会导致节点分裂进而导致数据表整体性能下降,那么问题来了主键自增是否会降低数据库insert性能呢?这里笔者会通过设置innodb_autoinc_lock_mode这个参数演示不同级别下主键自增的内部工作机制及其性能表现。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

前置准备

在正式开始实验之前,我们先创建一张实验表,其DDL语句如下,可以看到id是自增的,且a为索引:

CREATE TABLE `test_autoinc_lock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_a` (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

innodb_autoinc_lock_mode为0的插入

我们后续的插入实验都会围绕innodb_autoinc_lock_mode这个参数展开,将innodb_autoinc_lock_mode设置为0,使得指向插入操作时会获得依据表级auto_inc锁,它会在语句结束之后释放当前锁,注意这里所说的是语句级别下的锁,而不是事务级的锁(一个事务可能存在多个插入语句)。

我们先通过vim命令编辑/etc/mysql/my.cnf文件,在mysqld下手动将innodb_autoinc_lock_mode设置为0:

[mysqld]
innodb_autoinc_lock_mode = 0
skip-name-resolve

然后重启,此时我们就可以开始第一个插入实验了:

systemctl restart mysqld.service

假设我们此时的数据库的数据如下所示,可以看到数据为1-7

id|a|
--+-+
 1|1|
 2|2|
 3|3|
 4|4|
 5|5|
 6|6|
 7|7|

我们首先开启一个会话进行通过a作为条件条件删除,不提交该事务:

begin;delete from test_autoinc_lock where a>7;

在此时我们开启新的会话2,插入一个a为11的数据,很明显因为上述的范围删除使得大于7使得当前SQL处于gap锁进而出现阻塞:

insert into test_autoinc_lock(a) values(11);

再次开启一个会话3,再次进行一次插入,发现还是处于阻塞状态,这是为什么呢?

insert into test_autoinc_lock(a) values(1);

第一个范围删除锁定了大于7的范围,使得我们的第一条插入语句因为gap锁而阻塞,从而导致长期持有auto_inc lock不释放,进而出现插入数据不在大于7范围以内无辜的第二条插入语句阻塞:

这一点,我们可以通过information_schema.innodb_trxInnoDB 事务的相关信息表查看:

select * from information_schema.innodb_trx;

对应输出结果如下,可以看到插入数据为1的SQL等待插入11的SQL释放auto_inc锁,而插入11的数据因为delete语句的gap锁阻塞着:

trx_id|trx_state|trx_started        |trx_requested_lock_id|trx_wait_started   |trx_weight|trx_mysql_thread_id|trx_query                                                                                                             |trx_operation_state  |trx_tables_in_use|trx_tables_locked|trx_lock_structs|trx_lock_memory_bytes|trx_rows_locked|trx_rows_modified|trx_concurrency_tickets|trx_isolation_level|trx_unique_checks|trx_foreign_key_checks|trx_last_foreign_key_error|trx_adaptive_hash_latched|trx_adaptive_hash_timeout|trx_is_read_only|trx_autocommit_non_locking|
------+---------+-------------------+---------------------+-------------------+----------+-------------------+----------------------------------------------------------------------------------------------------------------------+---------------------+-----------------+-----------------+----------------+---------------------+---------------+-----------------+-----------------------+-------------------+-----------------+----------------------+--------------------------+-------------------------+-------------------------+----------------+--------------------------+
6673  |LOCK WAIT|2024-02-13 23:07:10|6673:59              |2024-02-13 23:07:10|         1|                  8|/* ApplicationName=DBeaver Enterprise 22.3.0 - SQLEditor <Script-10.sql> */ insert into test_autoinc_lock(a) values(1)|setting auto-inc lock|                1|                1|               1|                 1136|              0|                0|                      0|REPEATABLE READ    |                1|                     1|                          |                        0|                        0|               0|                         0|
6672  |LOCK WAIT|2024-02-13 23:07:06|6672:47:3:1          |2024-02-13 23:07:06|         3|                  7|/* ApplicationName=DBeaver Enterprise 22.3.0 - SQLEditor <Script-9.sql> */ insert into test_autoinc_lock(a) values(11)|inserting            |                1|                2|               3|                 1136|              1|                0|                      0|REPEATABLE READ    |                1|                     1|                          |                        0|                        0|               0|                         0|
6671  |RUNNING  |2024-02-13 23:07:03|                     |                   |         3|                  6|                                                                                                                      |                     |                0|                1|               3|                 1136|             11|                0|                      0|REPEATABLE READ    |                1|                     1|                          |                        0|                        0|               0|                         0|

来简单小结一下,对于MySQL5.6下的innodb_autoinc_lock_mode(默认为0),自增锁为重量级锁,在高并发的大量修改和插入操作时自增锁竞锁竞争是比较大的,很可能因为某个操作不当从而导致性能瓶颈。

innodb_autoinc_lock_mode为1的插入

我们再设置为1,还是以上述的SQL为例,进行3个会话操作,首先会话1的范围删除,持有gap锁:

begin;delete from test_autoinc_lock where a>7;

会话2再次插入在范围删除以内的数据,因为gap锁的存在,处于锁等待状态:

insert into test_autoinc_lock(a) values(11);

来到会话3,插入数据1,直接成功:

insert into test_autoinc_lock(a) values(1);

这种模式是MySQL的默认模式,在这种模式下进行插入时插入语句不会一致持有auto_inc锁到语句结束,而是一拿到锁就释放,这就是为什么插入11的语句因为gap锁阻塞不影响插入语句2自增id的获取。

如下图所示,第一条插入语句拿到11后等待gap锁释放,而插入语句2依然可以直接拿到自增id的值12:

这一点我们查看表数据即可印证,可以看到插入值为1的id为12:

id|a |
--+--+
 1| 1|
12| 1|
 2| 2|
 3| 3|
 4| 4|
 5| 5|
 6| 6|
 7| 7|
 8| 8|
 9| 9|
10|10|

innodb_autoinc_lock_mode为2的插入

配置为2是MySQL8默认配置级别,这种级别的性能表现和配置为1是一样的,只不过在binlog_formatstatement(复制master的语句)进行主从复制时会出现异常。 例如主库insert select时,其他会话insert一条数据其id为10088,因为设置的复制类型为statement,所以回放上述两个语句时,insert select可能也会拿到1088,进而导致主键冲突的异常:

小结

自此我们了解了不同的innodb_autoinc_lock_mode自增主键的插入性能表现,在底层的实现上,默认情况下MySQL的插入不会导致任何性能问题,作为开发者我们更应该关心的是进行插入期间,业务上是否会存在那些干扰插入的间隙锁的情况。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

参考

MySQL自增锁模式innodb_autoinc_lock_mode参数理解调优:https://www.cnblogs.com/widgetbox/p/10178035.html

自增主键是否会降低数据库insert性能?如果会的话为什么还有很多公司采用? - 姜健的回答 - 知乎:https://www.zhihu.com/question/52119372/answer/129256479

本文使用 markdown.com.cn 排版