共计 3050 个字符,预计需要花费 8 分钟才能阅读完成。
之前的文章把 InnoDB 中的所有的锁都介绍了一下,包含意向锁、记录锁 … 自增锁巴拉巴拉的。然而前面我本人回过头去看的时候发现,对自增锁的介绍竟然才短短的一段。
其实自增锁(AUTO-INC Locks)这块还是有很多值得探讨的细节,例如在并发的场景下,InnoDB 是如何保障该值正确的进行自增的,本章就专门来 简略 讨论一下 InnoDB 中的自增锁。
什么是自增锁
之前咱们提到过,自增锁是一种比拟非凡的 表级锁 。并且在事务向蕴含了 AUTO_INCREMENT
列的表中新增数据时就会去持有自增锁,假如事务 A 正在做这个操作,如果另一个事务 B 尝试执行 INSERT
语句,事务 B 会被阻塞住,直到事务 A 开释自增锁。
这怎么说呢,说他对,然而他也不齐全对。
行为与限度
其实下面说的那种阻塞状况只是自增锁行为的 其中一种,能够了解为自增锁就是一个接口,其具体的实现有多种。具体的配置项为 innodb_autoinc_lock_mode
,通过这个配置项咱们能够扭转自增锁中运行的一些细节。
并且,自增锁还有一个限度,那就是被设置为 AUTO_INCREMENT
的列必须是索引,或者该列是索引的一部分(联结索引),不过这个限度对于大部分开发场景下并没有什么影响。
毕竟咱们的基操不就是把 id 设置为
AUTO_INCREMENT
吗。
锁模式
其实在 InnoDB 中,把锁的行为叫做 锁模式 可能更加精确,那具体有哪些锁模式呢,如下:
- 传统模式(Traditional)
- 间断模式(Consecutive)
- 穿插模式(Interleaved)
别离对应配置项 innodb_autoinc_lock_mode
的值 0、1、2.
看到这就曾经晓得为啥下面说不精确了,因为三种模式下,InnoDB 对并发的解决是不一样的,而且具体抉择哪种锁模式跟你以后应用的 MySQL 版本还有关系。
在 MySQL 8.0 之前,InnoDB 锁模式默认为 间断模式 ,值为 1,而在 MySQL 8.0 之后,默认模式变成了 穿插模式。至于为啥会扭转默认模式,前面会讲。
传统模式
传统模式(Traditional),说白了就是还没有 锁模式 这个概念时,InnoDB 的自增锁运行的模式。只是前面版本更新,InnoDB 引入了 锁模式 的概念,而后 InnoDB 给了这种以前默认的模式一个名字,叫——传统模式。
传统模式具体是咋工作的?
咱们晓得,当咱们向蕴含了 AUTO_INCREMENT
列的表中插入数据时,都会持有这么一个非凡的表锁——自增锁(AUTO-INC),并且当语句执行完之后就会开释。这样一来能够保障单个语句内生成的自增值是间断的。
这样一来,传统模式的弊病就天然裸露进去了,如果有多个事务并发的执行 INSERT
操作,AUTO-INC
的存在会使得 MySQL 的性能略有降落,因为同时只能执行一条 INSERT
语句。
间断模式
间断模式(Consecutive)是 MySQL 8.0 之前默认的模式,之所以提出这种模式,是因为传统模式存在影响性能的弊病,所以才有了间断模式。
在锁模式处于间断模式下时,如果 INSERT
语句可能提前确定插入的数据量,则能够不必获取自增锁,举个例子,像 INSERT INTO
这种简略的、能提前确认数量的新增语句,就不会应用自增锁,这个很好了解,在自增值上,我能够间接把这个 INSERT
语句所须要的空间流进去,就能够继续执行下一个语句了。
然而如果 INSERT
语句不能提前确认数据量,则还是会去获取自增锁。例如像 INSERT INTO ... SELECT ...
这种语句,INSERT
的值来源于另一个 SELECT
语句。
间断模式的图和穿插模式差不多
穿插模式
穿插模式(Interleaved)下,所有的 INSERT
语句,蕴含 INSERT
和 INSERT INTO ... SELECT
,都不会应用 AUTO-INC
自增锁,而是应用较为轻量的 mutex
锁。这样一来,多条 INSERT
语句能够并发的执行,这也是三种锁模式中扩展性最好的一种。
并发执行所带来的副作用就是单个 INSERT
的自增值并不间断,因为 AUTO_INCREMENT
的值调配会在多个 INSERT
语句中来回穿插的执行。
长处很明确,毛病是在并发的状况下无奈保证数据一致性,这个上面会探讨。
穿插模式缺点
要理解缺点是什么,还得先理解一下 MySQL 的 Binlog。Binlog 个别用于 MySQL 的 数据复制,艰深一点就是用于主从同步。在 MySQL 中 Binlog 的格局有 3 种,别离是:
- Statement 基于语句,只记录对数据做了批改的 SQL 语句,可能无效的缩小 binlog 的数据量,进步读取、基于 binlog 重放的性能
- Row 只记录被批改的行,所以 Row 记录的 binlog 日志量一般来说会比 Statement 格局要多。基于 Row 的 binlog 日志十分残缺、清晰,记录了所有数据的变动,然而毛病是可能会十分多,例如一条
update
语句,有可能是所有的数据都有批改;再例如alter table
之类的,批改了某个字段,同样的每条记录都有改变。 - Mixed Statement 和 Row 的联合,怎么个结合法呢。例如像
alter table
之类的对表构造的批改,采纳 Statement 格局。其余的对数据的批改例如update
和delete
采纳 Row 格局进行记录。
如果 MySQL 采纳的格局为 Statement
,那么 MySQL 的主从同步实际上同步的就是一条一条的 SQL 语句。如果此时咱们采纳了穿插模式,那么并发状况下 INSERT
语句的执行程序就无奈失去保障。
可能你还没看出问题在哪儿,INSERT
同时穿插执行,并且 AUTO_INCREMENT
穿插调配将会间接导致主从之间同行的数据 主键 ID 不同。而这对主从同步来说是灾难性的。
换句话说,如果你的 DB 有主从同步,并且 Binlog 存储格局为 Statement,那么不要将 InnoDB 自增锁模式设置为穿插模式,会有问题。其实主从同步的过程远比上图中的简单,之前我也写过具体的 MySQL 主从同步的文章,感兴趣能够先去看看。
而起初,MySQL 将日志存储格局从 Statement
变成了 Row
,这样一来,主从之间同步的就是实在的行数据了,而且 主键 ID
在同步到从库之前曾经确定了,就对同步语句的程序并不敏感,就躲避了下面 Statement
的问题。
基于 MySQL 默认 Binlog 格局从 Statement
到 Row
的变更,InnoDB 也将其自增锁的默认实现从间断模式,更换到了效率更高的 穿插模式。
鱼和熊掌
然而如果你的 MySQL 版本依然默认应用 间断模式,但同时又想要进步性能,该怎么办呢?这个其实得做一些取舍。
如果你能够判定你的零碎后续不会应用 Binlog,那么你能够抉择将自增锁的锁模式从 间断模式 改为 穿插模式,这样能够进步 MySQL 的并发。并且,没有了主从同步,INSERT
语句在从库乱序执行导致的 AUTO_INCREMENT
值不匹配的问题也就天然不会遇到了。
总结
你可能会说,为啥要理解这么深?有啥用?
其实还真有,例如在业务中你有一个须要执行 几十秒 的脚本,脚本中不停的调用屡次 INSERT
,这时就问你这个问题,在这几十秒里,会阻塞其余的用户应用对应的性能吗?
如果你对自增锁有足够的理解,那么这个问题将会迎刃而解。
本篇文章已放到我的 Github github.com/sh-blog 中,欢送 Star。微信搜寻关注【SH 的全栈笔记 】,回复【 队列】获取 MQ 学习材料,蕴含根底概念解析和 RocketMQ 具体的源码解析,继续更新中。
如果你感觉这篇文章对你有帮忙,还麻烦 点个赞 , 关个注 , 分个享 , 留个言。