本文转载自微信公众号《SH的全栈笔记》,作者SH的全栈笔记。转载本文请联系SH全栈笔记公众号。上一篇介绍了InnoDB中所有的锁,包括意向锁、记录锁……自增锁。但是后来回过头来看,发现自增锁的介绍只是短短的一段话。其实关于自增锁(AUTO-INCLocks)还有很多值得讨论的细节。比如在并发场景下,InnoDB如何保证值正确自增?本章专门简要讨论InnoDB。自增锁。什么是自增锁我们之前提到自增锁是一种特殊的表级锁。而当事务向包含AUTO_INCREMENT列的表添加数据时,会持有自增锁。假设事务A正在做这个操作,如果另一个事务B试图执行INSERT语句,事务B将被阻塞,直到事务A释放自增锁。怎么说呢,说他是对的,但也不完全是对的。行为与限制其实,上面提到的阻塞情况只是自增行为中的一种。可以理解为自增锁是一个接口,具体实现有很多种。具体的配置项是innodb_autoinc_lock_mode,通过它我们可以改变autoinclock中的一些操作细节。另外自增还有一个限制,就是设置为AUTO_INCREMENT的列必须是索引,或者列必须是索引的一部分(联合索引),但是这个限制对大部分开发场景没有影响。毕竟我们的基本操作不就是设置id为AUTO_INCREMENT吗?锁模式其实在InnoDB中,将锁行为称为锁模式可能更准确。具体有哪些锁模式,如下:Traditionalmode,Consecutivemode,Interleavedmode对应配置项innodb_autoinc_lock_mode的值0,1,2。看到这里,你已经知道为什么上面说的不准确了,因为在三种模式中,InnoDB对并发的处理是不同的,选择哪种锁模式跟你当前使用的MySQL版本也有关系。在MySQL8.0之前,InnoDB锁模式默认为连续模式,值为1,但在MySQL8.0之后,默认模式变成了交错模式。至于为什么要改默认模式,后面再说。传统模式传统模式(Traditionalmode),说白了,就是在没有锁模式概念的情况下,InnoDB的自增锁运行的模式。只是后来的一次版本更新,InnoDB引入了锁模式的概念,然后InnoDB给这个曾经的默认模式起了个名字,叫做传统模式。传统模式如何运作?我们知道,当我们向包含AUTO_INCREMENT列的表中插入数据时,会持有这样一种特殊的表锁——自增锁(AUTO-INC),当语句执行完毕后就会释放。这样可以保证单条语句内产生的自增值是连续的。这样一来,传统模式的弊端自然暴露无遗。如果有多个事务并发执行INSERT操作,AUTO-INC的存在会稍微降低MySQL的性能,因为同一时间只能执行一个INSERT语句。连续模式连续模式(Consecutive)是MySQL8.0之前的默认模式。之所以提出这种模式,是因为传统模式存在影响性能的缺点,所以才有了连续模式。当锁模式为连续模式时,如果INSERT语句可以提前确定插入的数据量,则不需要获取自增锁。比如INSERTINTO这种简单的new语句,可以提前确定数量,只是不知道怎么用自增锁。这很容易理解。在自增方面,我可以直接把INSERT语句需要的空间流出来,然后继续执行下一条语句。但是如果INSERT语句无法提前确认数据量,还是会获取自增锁。例如,像INSERTINTO...SELECT...这样的语句,其中INSERT的值来自另一个SELECT语句。连续模式的图形类似于交错模式。在交错模式(Interleaved)下,所有的INSERT语句,包括INSERT和INSERTINTO...SELECT,都不会使用AUTO-INC自增锁,而是使用相对轻量级的互斥锁。这样可以并发执行多个INSERT语句,这也是三种锁模式中扩展性最强的。并发执行的副作用是单条INSERT的自增值不连续,因为AUTO_INCREMENT的赋值会在多条INSERT语句中来回执行。优点很明显,缺点是在并发情况下无法保证数据的一致性,下面会讲到。跨模式缺陷要了解缺陷是什么,首先要了解MySQL的Binlog。Binlog一般用于MySQL的数据复制,常用于主从同步。MySQL中的Binlog有三种格式,分别是:StatementBasedonstatements,只记录修改数据的SQL语句,可以有效减少binlog的数据量,提高读取和重放的性能基于binlogRow只记录修改过的行,所以Row记录的binlog日志量一般要多于Statement格式。Row-basedbinlog日志非常完整清晰,记录了所有的数据变化,但缺点是可能有很多条,比如一条update语句,所有的数据都可能被修改;再比如altertable等,修改了A字段,同样每条记录都有变化。MixedStatement和Row的组合是什么?比如altertable等表结构的修改就采用Statement格式。其他对数据的修改,如update、delete,都记录在Row格式中。如果MySQL采用的格式是Statement,那么MySQL的主从同步实际上就是一条一条的同步SQL语句。如果此时我们使用交叉模式,INSERT语句的执行顺序在并发情况下是无法保证的。可能你还没看出问题,INSERT同时执行,AUTO_INCREMENT交叉赋值会直接导致master和slave之间peer的数据主键ID不同。而这对于主从同步来说是灾难性的。也就是说,如果你的DB是主从同步的,Binlog存储格式是Statement,那InnoDB的自增模式就不要设置成cross模式,会出问题。其实主从同步的过程远比上图复杂。之前写过一篇关于MySQL主从同步的详细文章。如果您有兴趣,可以先阅读。后来MySQL将日志存储格式从Statement改为Row。这样主从就同步了真正的行数据,主键ID在同步到从库之前就已经确定了。顺序不敏感,避免了上面Statement的问题。基于MySQL默认的Binlog格式由Statement改为Row,InnoDB也将自增锁的默认实现由连续模式改为更高效的交叉模式。鱼和熊掌但是如果你的MySQL版本仍然默认使用连续模式,但同时又想提高性能怎么办?这实际上需要一些权衡。如果你能断定你的系统以后不会使用Binlog,那么你可以选择将自增锁模式从连续模式改为交错模式,这样可以提高MySQL的并发性。而且,如果没有主从同步,自然不会遇到从库中INSERT语句乱序执行导致的AUTO_INCREMENT值不匹配的问题。总结一下,你可能会说,你为什么要了解这么深?有什么用?其实有的,比如说你的业务,你有一个脚本需要执行几十秒,脚本一直在多次调用INSERT。这时候问问你的问题会不会在这几十秒内导致其他用户无法使用相应的功能?如果你对自增锁有足够的了解,那么这个问题就迎刃而解了
