InnoDB
提供了一种可配置的锁定机制,可以显着提高将行添加到具有
AUTO_INCREMENT
列的表的 SQL 语句的可伸缩性和性能。要在
表中
使用该AUTO_INCREMENT
机制,
必须将列定义为某个索引的第一列或唯一列,以便可以对表执行相当于索引查找的操作以获得最大列值。索引不需要是or
,但为了避免
列中的重复值,建议使用这些索引类型。
InnoDB
AUTO_INCREMENT
SELECT
MAX(
ai_col
)PRIMARY KEY
UNIQUE
AUTO_INCREMENT
本节介绍AUTO_INCREMENT
锁定模式、不同锁定模式设置的使用含义
AUTO_INCREMENT
以及如何
InnoDB
初始化
AUTO_INCREMENT
计数器。
本节介绍AUTO_INCREMENT
用于生成自动增量值的锁定模式,以及每种锁定模式如何影响复制。自增锁定模式是在启动时使用
innodb_autoinc_lock_mode
变量配置的。
以下术语用于描述
innodb_autoinc_lock_mode
设置:
“
INSERT
-like ” 语句在表中生成新行的所有语句,包括
INSERT
、INSERT ... SELECT
、REPLACE
、REPLACE ... SELECT
和LOAD DATA
。包括“简单插入”、 “批量插入”和“混合模式” 插入。“简单的插入”
可以预先确定要插入的行数的语句(当语句最初被处理时)。这包括单行和多行
INSERT
以及REPLACE
没有嵌套子查询的语句,但不包括INSERT ... ON DUPLICATE KEY UPDATE
.“批量插入”
事先不知道要插入的行数(以及所需的自动增量值的数量)的语句。这包括
INSERT ... SELECT
、REPLACE ... SELECT
和LOAD DATA
语句,但不包括普通INSERT
的 。在处理每一行时,一次为列InnoDB
分配一个新值。AUTO_INCREMENT
“混合模式插入”
这些是“简单插入”语句,它们为一些(但不是全部)新行指定自动增量值。下面是一个示例,其中
c1
是表的一AUTO_INCREMENT
列t1
:INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
另一种类型的“混合模式插入”是
INSERT ... ON DUPLICATE KEY UPDATE
,在最坏的情况下实际上是 anINSERT
后跟 a ,其中在更新阶段可能会或可能不会使用列UPDATE
的分配值 。AUTO_INCREMENT
innodb_autoinc_lock_mode
该变量
有三种可能的设置
。设置为 0、1 或 2,分别表示
“传统”、“连续”或
“交错”锁定模式。从 MySQL 8.0 开始,交错锁定模式 ( innodb_autoinc_lock_mode=2
) 是默认设置。在 MySQL 8.0 之前,连续锁定模式是默认的 ( innodb_autoinc_lock_mode=1
)。
MySQL 8.0 中交错锁模式的默认设置反映了从基于语句的复制到基于行的复制作为默认复制类型的变化。Statement-based复制需要连续的自增锁模式来保证给定的SQL语句序列的自增值按照可预测、可重复的顺序赋值,而row-based复制对SQL语句的执行顺序不敏感.
innodb_autoinc_lock_mode = 0
(“传统”锁定模式)innodb_autoinc_lock_mode
传统的锁定模式提供了与引入变量 之前相同的行为 。由于语义上可能存在差异,提供传统锁定模式选项是为了向后兼容、性能测试和解决“混合模式插入”的问题。在这种锁定模式下,所有“ INSERT-like ”语句都获得一个特殊的表级
AUTO-INC
锁,用于插入到具有AUTO_INCREMENT
列的表中。此锁通常保持到语句的末尾(而不是事务的末尾),以确保为给定的语句序列以可预测和可重复的顺序分配自动增量值INSERT
,并确保自动增量值由任何给定语句分配的是连续的。对于基于语句的复制,这意味着当在副本服务器上复制 SQL 语句时,自动增量列使用与源服务器上相同的值。多条语句的执行结果
INSERT
是确定的,副本重现与源相同的数据。如果多个语句生成的自动增量值INSERT
交错,则两个并发INSERT
语句的结果将是不确定的,并且无法可靠地传播到使用基于语句的复制的副本服务器。为清楚起见,请考虑使用此表的示例:
CREATE TABLE t1 ( c1 INT(11) NOT NULL AUTO_INCREMENT, c2 VARCHAR(10) DEFAULT NULL, PRIMARY KEY (c1) ) ENGINE=InnoDB;
假设有两个事务正在运行,每个事务都将行插入到包含一
AUTO_INCREMENT
列的表中。一个事务使用INSERT ... SELECT
插入 1000 行的语句,另一个事务使用插入一行的简单INSERT
语句:Tx1: INSERT INTO t1 (c2) SELECT 1000 rows from another table ... Tx2: INSERT INTO t1 (c2) VALUES ('xxx');
InnoDB
无法预先知道从 Tx1SELECT
中的INSERT
语句中检索了多少行,并且随着语句的进行,它一次分配一个自动增量值。使用表级锁,保持到语句末尾,一次只能执行一条INSERT
引用表的语句t1
,不同语句生成自增数不会交错。Tx1 语句生成的自增值INSERT ... SELECT
是连续的,并且使用的(单个)自增值INSERT
Tx2 中的语句小于或大于用于 Tx1 的所有语句,具体取决于首先执行的语句。只要 SQL 语句在从二进制日志重播时(使用基于语句的复制时,或在恢复场景中)以相同的顺序执行,结果就与 Tx1 和 Tx2 首次运行时相同。因此,表级锁一直保持到语句结束,使
INSERT
使用自动增量的语句可以安全地用于基于语句的复制。但是,当多个事务同时执行插入语句时,那些表级锁会限制并发性和可伸缩性。在前面的示例中,如果没有表级锁,则用于
INSERT
in Tx2 的自增列的值恰好取决于语句执行的时间。如果INSERT
of Tx2 在INSERT
of Tx1 运行时执行(而不是在它开始之前或它完成之后),则这两个语句分配的特定自动增量值INSERT
是不确定的,并且可能因运行而异。在 连续 锁模式下,
InnoDB
可以避免对行数预先已知的 “简单插入”AUTO-INC
语句使用表级锁 ,并且仍然为基于语句的复制保留确定性执行和安全性。如果您不使用二进制日志来重放 SQL 语句作为恢复或复制的一部分, 则可以使用交错 锁定模式来消除对表级
AUTO-INC
锁的所有使用,以实现更高的并发性和性能,但代价是允许 auto 中的间隙-递增语句分配的数字,并可能交错并发执行的语句分配的数字。innodb_autoinc_lock_mode = 1
(“连续”锁定模式)在这种模式下,“批量插入”使用特殊的
AUTO-INC
表级锁并将其保持到语句结束。这适用于所有INSERT ... SELECT
、REPLACE ... SELECT
和LOAD DATA
语句。一次只能执行一条持有AUTO-INC
锁的语句。如果批量插入操作的源表与目标表不同,则AUTO-INC
在从源表中选择的第一行获得共享锁后,再获得目标表的锁。如果批量插入操作的源和目标是同一张表,则AUTO-INC
在对所有选定行获取共享锁后获取锁。“简单插入”(预先知道要插入的行数)
AUTO-INC
通过在互斥锁(轻量级锁)的控制下获得所需数量的自动增量值来避免表级锁在分配过程期间持有,直到语句完成。AUTO-INC
除非AUTO-INC
锁被另一个事务持有,否则不使用表级 锁。如果另一个事务持有AUTO-INC
锁,则“简单插入”等待AUTO-INC
锁,就好像它是“批量插入”。这种锁定模式确保,在存在
INSERT
行数事先未知的语句(以及自动递增数字随着语句的进行分配)的情况下,任何 “INSERT
-like ” 语句分配的所有自动递增值都是连续的,并且操作对于基于语句的复制是安全的。简而言之,这种锁定模式显着提高了可伸缩性,同时可以安全地用于基于语句的复制。此外,与“传统” 锁定模式一样,任何给定语句分配的自动递增数字都是连续的。对于 任何使用自动递增的语句, 与“传统”模式相比,语义 没有变化,但有一个重要的例外。
例外是“混合模式插入”
AUTO_INCREMENT
,其中用户为多行“简单插入”中的一些(但不是全部)行的列提供显式值 。对于此类插入,InnoDB
分配比要插入的行数更多的自动增量值。但是,所有自动分配的值都是连续生成的(因此高于)由最近执行的先前语句生成的自动增量值。“多余”的号码丢失。innodb_autoinc_lock_mode = 2
(“交错”锁定模式)在这种锁模式下,没有 “
INSERT
-like ” 语句使用表级AUTO-INC
锁,可以同时执行多条语句。这是最快和最具扩展性的锁模式,但在使用基于语句的复制或从二进制日志重放 SQL 语句的恢复场景时 ,它 并不安全。在这种锁定模式下,自动增量值保证是唯一的,并且在所有并发执行 的“
INSERT
-like ” 语句中单调递增。但是,由于多个语句可以同时生成数字(即,数字的分配在语句之间交错),因此为任何给定语句插入的行生成的值可能不是连续的。如果唯一执行的语句是“简单插入”,其中要插入的行数提前已知,则为单个语句生成的数字没有间隙, “混合模式插入”除外。但是,当执行“批量插入”时,任何给定语句分配的自动增量值可能存在间隙。
在复制中使用自动增量
如果您使用的是基于语句的复制,请设置
innodb_autoinc_lock_mode
为 0 或 1 并在源及其副本上使用相同的值。innodb_autoinc_lock_mode
如果您使用= 2(“交错”)或源和副本不使用相同锁定模式的配置, 则不能确保副本上的自动增量值与源上 的值相同。如果您使用基于行或混合格式的复制,所有的自增锁模式都是安全的,因为基于行的复制对 SQL 语句的执行顺序不敏感(并且混合格式使用基于行的对基于语句的复制不安全的任何语句进行复制)。
“丢失”自动增量值和序列间隙
在所有锁模式(0、1、2)下,如果一个产生自增值的事务回滚,那些自增值就会“丢失”。自增列一旦产生值,就不能回滚,不管 “
INSERT
-like ” 语句是否完成,包含的事务是否回滚。这些丢失的值不会被重用。AUTO_INCREMENT
因此,存储在表的列 中的值可能存在间隙 。AUTO_INCREMENT
为列 指定 NULL 或 0在所有锁定模式(0、1 和 2)中,如果用户为 中的
AUTO_INCREMENT
列 指定 NULL 或 0INSERT
, 则InnoDB
将该行视为未指定值并为其生成一个新值。AUTO_INCREMENT
为列 分配负值在所有锁定模式(0、1 和 2)中,如果您为列分配负值,则自动递增机制的行为是未定义的
AUTO_INCREMENT
。如果该
AUTO_INCREMENT
值变得大于指定整数类型的最大整数在所有锁定模式(0、1 和 2)中,如果值变得大于可以存储在指定整数类型中的最大整数,则自动递增机制的行为是未定义的。
“批量插入” 的自动增量值差距
innodb_autoinc_lock_mode
设置为 0( “ 传统”)或 1(“连续”)时,任何给定语句生成的自动增量值都是连续的,没有间隙,因为表级AUTO-INC
锁一直保持到语句结束,并且只一次可以执行一个这样的语句。innodb_autoinc_lock_mode
设置为 2( “ interleaved ”)时, “批量插入”生成的自动增量值可能存在间隙,但前提是同时执行 “INSERT
-like ” 语句。对于锁定模式 1 或 2,连续语句之间可能会出现间隙,因为对于批量插入,可能不知道每个语句所需的自动增量值的确切数量,并且可能会高估。
由“混合模式插入” 分配的自动增量值
考虑一个“混合模式插入”,其中 “简单插入”指定一些(但不是全部)结果行的自动增量值。这样的语句在锁定模式 0、1 和 2 中的行为不同。例如,假设
c1
是 table 的AUTO_INCREMENT
列t1
,并且最近自动生成的序列号是 100。mysql> CREATE TABLE t1 ( -> c1 INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, -> c2 CHAR(1) -> ) ENGINE = INNODB;
现在,考虑以下“混合模式插入” 语句:
mysql> INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
innodb_autoinc_lock_mode
设置为 0( “ 传统”)时,四个新行是:mysql> SELECT c1, c2 FROM t1 ORDER BY c2; +-----+------+ | c1 | c2 | +-----+------+ | 1 | a | | 101 | b | | 5 | c | | 102 | d | +-----+------+
下一个可用的自动增量值是 103,因为自动增量值是一次分配一个,而不是在语句执行开始时一次分配。无论是否同时执行 “
INSERT
-like ” 语句(任何类型),此结果都是正确的。innodb_autoinc_lock_mode
设置为 1( “ 连续”)时,四个新行也是:mysql> SELECT c1, c2 FROM t1 ORDER BY c2; +-----+------+ | c1 | c2 | +-----+------+ | 1 | a | | 101 | b | | 5 | c | | 102 | d | +-----+------+
但是,在这种情况下,下一个可用的自动增量值是 105,而不是 103,因为在处理语句时分配了四个自动增量值,但只使用了两个。无论是否同时执行 “
INSERT
-like ” 语句(任何类型),此结果都是正确的。innodb_autoinc_lock_mode
设置为 2( “ 交错”)时,四个新行是:mysql> SELECT c1, c2 FROM t1 ORDER BY c2; +-----+------+ | c1 | c2 | +-----+------+ | 1 | a | | x | b | | 5 | c | | y | d | +-----+------+
x
和 的值y
是唯一的,并且比之前生成的任何行都大。但是,和的具体取值x
取决于y
并发执行语句产生的自增值的个数。最后,考虑以下语句,在最近生成的序列号为 100 时发出:
mysql> INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (101,'c'), (NULL,'d');
对于任何
innodb_autoinc_lock_mode
设置,此语句都会生成重复键错误 23000 (Can't write; duplicate key in table
),因为为该行分配了 101,并且该行的(NULL, 'b')
插入(101, 'c')
失败。AUTO_INCREMENT
在一系列INSERT
语句 的中间 修改列值在 MySQL 5.7 及更早版本中,修改
AUTO_INCREMENT
一系列语句中间的列值INSERT
可能会导致“重复条目” 错误。例如,如果您执行的UPDATE
操作将AUTO_INCREMENT
列值更改为大于当前最大自动增量值的值,INSERT
则未指定未使用的自动增量值的后续操作可能会遇到“重复条目”错误。在 MySQL 8.0 及更高版本中,如果您修改一个AUTO_INCREMENT
列值大于当前最大自增值的值,新值被持久化,后续INSERT
操作从新的、更大的值开始分配自增值。以下示例演示了此行为。mysql> CREATE TABLE t1 ( -> c1 INT NOT NULL AUTO_INCREMENT, -> PRIMARY KEY (c1) -> ) ENGINE = InnoDB; mysql> INSERT INTO t1 VALUES(0), (0), (3); mysql> SELECT c1 FROM t1; +----+ | c1 | +----+ | 1 | | 2 | | 3 | +----+ mysql> UPDATE t1 SET c1 = 4 WHERE c1 = 1; mysql> SELECT c1 FROM t1; +----+ | c1 | +----+ | 2 | | 3 | | 4 | +----+ mysql> INSERT INTO t1 VALUES(0); mysql> SELECT c1 FROM t1; +----+ | c1 | +----+ | 2 | | 3 | | 4 | | 5 | +----+
本节介绍如何InnoDB
初始化
AUTO_INCREMENT
计数器。
如果您AUTO_INCREMENT
为表指定一个列,InnoDB
内存表对象包含一个称为自动递增计数器的特殊计数器,在为该列分配新值时使用该计数器。
在 MySQL 5.7 及更早版本中,自增计数器存储在主内存中,而不是磁盘上。要在服务器重新启动后初始化自动递增计数器,InnoDB
将在第一次插入包含
AUTO_INCREMENT
列的表时执行与以下语句等效的语句。
SELECT MAX(ai_col) FROM table_name FOR UPDATE;
在 MySQL 8.0 中,此行为已更改。当前最大自增计数器值在每次更改时写入重做日志,并在每个检查点保存到数据字典。这些更改使当前最大自动递增计数器值在服务器重新启动后保持不变。
在服务器正常关闭后重新启动时,
InnoDB
使用存储在数据字典中的当前最大自动增量值初始化内存中的自动增量计数器。
在崩溃恢复期间服务器重启时,
InnoDB
使用存储在数据字典中的当前最大自动增量值初始化内存中的自动增量计数器,并扫描重做日志以查找自上次检查点以来写入的自动增量计数器值。如果重做记录值大于内存中计数器值,则应用重做记录值。但是,在服务器意外退出的情况下,无法保证重用先前分配的自动增量值。每次当前最大自动增量值因
INSERT
或
而改变UPDATE
操作时,新值写入重做日志,但如果在重做日志刷新到磁盘之前发生意外退出,则在服务器重新启动后初始化自增计数器时可以重用之前分配的值。
InnoDB
使用等效
语句来初始化自动递增计数器
的唯一情况是在导入
没有元数据文件的表时。否则,如果存在,则从元数据文件中读取当前最大自动递增计数器值。除了计数器值初始化之外,
当尝试将计数器值设置为小于或等于使用SELECT MAX(ai_col) FROM
table_name
FOR UPDATE.cfg
.cfg
SELECT MAX(ai_col) FROM
table_name
ALTER TABLE ... AUTO_INCREMENT =
陈述。例如,您可能会在删除某些记录后尝试将计数器值设置为较小的值。在这种情况下,必须查表以确保新的计数器值不小于或等于实际的当前最大计数器值。
N
FOR UPDATE
在 MySQL 5.7 及更早版本中,服务器重启会取消AUTO_INCREMENT = N
table 选项的影响,该选项可用于CREATE TABLE
or
ALTER TABLE
语句中以分别设置初始计数器值或更改现有计数器值。在 MySQL 8.0 中,服务器重启不会取消
AUTO_INCREMENT = N
表选项的影响。如果将自动递增计数器初始化为特定值,或者如果将自动递增计数器值更改为更大的值,则新值将在服务器重新启动后保留。
ALTER TABLE ...
AUTO_INCREMENT = N
只能将自动递增计数器值更改为大于当前最大值的值。
在 MySQL 5.7 及更早版本中,服务器在ROLLBACK
操作后立即重启可能会导致重新使用先前分配给回滚事务的自动增量值,从而有效地回滚当前最大自动增量值。在 MySQL 8.0 中,当前的最大自动增量值被持久化,防止重复使用以前分配的值。
如果SHOW TABLE STATUS
语句在初始化自动递增计数器之前检查表,则InnoDB
打开表并使用存储在数据字典中的当前最大自动递增值初始化计数器值。然后将该值存储在内存中,供以后插入或更新使用。计数器值的初始化使用对表的正常排他锁定读取,该读取持续到事务结束。InnoDB
为新创建的表初始化自动增量计数器时遵循相同的过程,该表的用户指定的自动增量值大于 0。
自增计数器初始化后,如果在插入行时没有显式指定自增值,
InnoDB
则隐式自增计数器并将新值赋给列。如果插入一行显式指定自动递增列值,并且该值大于当前最大计数器值,则计数器设置为指定值。
InnoDB
只要服务器运行,就会使用内存中的自动递增计数器。当服务器停止并重新启动时,InnoDB
重新初始化自动递增计数器,如前所述。
该变量确定列值auto_increment_offset
的起点
。AUTO_INCREMENT
默认设置为 1。
该auto_increment_increment
变量控制连续列值之间的间隔。默认设置为 1。