文档首页
MySQL 8.4 参考手册
相关文档 下载本手册
PDF (US Ltr) - 39.9Mb
PDF (A4) - 40.0Mb
手册页 (TGZ) - 258.5Kb
手册页 (Zip) - 365.5Kb
信息 (Gzip) - 4.0Mb
信息 (Zip) - 4.0Mb


MySQL 8.4 参考手册  /  ...  /  InnoDB 中的 AUTO_INCREMENT 处理

17.6.1.6 InnoDB 中的 AUTO_INCREMENT 处理

InnoDB 提供了一种可配置的锁定机制,可以显著提高将行添加到具有 AUTO_INCREMENT 列的表的 SQL 语句的可扩展性和性能。要在 InnoDB 表中使用 AUTO_INCREMENT 机制,必须将 AUTO_INCREMENT 列定义为某个索引的第一个或唯一列,以便可以执行等效于索引 SELECT MAX(ai_col) 查找以获取最大列值。索引不需要是 PRIMARY KEYUNIQUE,但为了避免 AUTO_INCREMENT 列中的重复值,建议使用这些索引类型。

本节介绍 AUTO_INCREMENT 锁定模式、不同 AUTO_INCREMENT 锁定模式设置的使用含义,以及 InnoDB 如何初始化 AUTO_INCREMENT 计数器。

InnoDB AUTO_INCREMENT 锁定模式

本节介绍用于生成自动递增值的 AUTO_INCREMENT 锁定模式,以及每种锁定模式如何影响复制。自动递增锁定模式在启动时使用 innodb_autoinc_lock_mode 变量进行配置。

以下术语用于描述 innodb_autoinc_lock_mode 设置

  • INSERT 语句

    所有在表中生成新行的语句,包括 INSERTINSERT ... SELECTREPLACEREPLACE ... SELECTLOAD DATA。包括“简单插入”、“批量插入”和“混合模式”插入。

  • 简单插入

    可以预先确定要插入的行数的语句(在初始处理语句时)。这包括没有嵌套子查询的单行和多行 INSERTREPLACE 语句,但不包括 INSERT ... ON DUPLICATE KEY UPDATE

  • 批量插入

    预先不知道要插入的行数(以及所需的自动递增值的数量)的语句。这包括 INSERT ... SELECTREPLACE ... SELECTLOAD DATA 语句,但不包括普通的 INSERTInnoDB 在处理每一行时为 AUTO_INCREMENT 列分配新值。

  • 混合模式插入

    这些是为某些(但不是所有)新行指定自动递增值的“简单插入”语句。下面是一个示例,其中 c1 是表 t1AUTO_INCREMENT

    INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');

    另一种 “混合模式插入” 是 INSERT ... ON DUPLICATE KEY UPDATE,它在最坏的情况下实际上是一个 INSERT 后跟一个 UPDATE,其中在更新阶段可能会也可能不会使用为 AUTO_INCREMENT 列分配的值。

innodb_autoinc_lock_mode 变量有三种可能的设置。这些设置分别是 0、1 或 2,分别对应于 “传统”、 “连续” 或 “交错” 锁定模式。交错锁定模式 (innodb_autoinc_lock_mode=2) 是默认设置。

MySQL 8.4 中交错锁定模式的默认设置反映了默认复制类型从基于语句的复制到基于行的复制的转变。基于语句的复制需要连续的自增锁定模式,以确保为给定的 SQL 语句序列以可预测和可重复的顺序分配自增值,而基于行的复制对 SQL 语句的执行顺序不敏感。

  • innodb_autoinc_lock_mode = 0 (“传统” 锁定模式)

    传统锁定模式提供了引入 innodb_autoinc_lock_mode 变量之前的相同行为。提供传统锁定模式选项是为了实现向后兼容性、性能测试以及解决由于语义可能存在差异而导致的“混合模式插入”问题。

    在此锁定模式下,所有 “类似 INSERT 的” 语句都会获取一个特殊的表级 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 无法事先知道从 Tx1 中的 INSERT 语句的 SELECT 中检索到多少行,并且它会在语句进行时逐个分配自动增量值。使用表级锁(保持到语句结束),一次只能执行一个引用表 t1INSERT 语句,并且不同语句生成的自动增量编号不会交错。Tx1 INSERT ... SELECT 语句生成的自动增量值是连续的,并且 Tx2 中的 INSERT 语句使用的(单个)自动增量值小于或大于 Tx1 使用的所有自动增量值,具体取决于哪个语句先执行。

    只要在从二进制日志重播 SQL 语句时(使用基于语句的复制时,或在恢复方案中)以相同的顺序执行 SQL 语句,结果将与 Tx1 和 Tx2 首次运行时的结果相同。因此,保持到语句结束的表级锁使使用自动增量的 INSERT 语句可以安全地与基于语句的复制一起使用。但是,当多个事务同时执行插入语句时,这些表级锁会限制并发性和可伸缩性。

    在前面的示例中,如果没有表级锁,则用于 Tx2 中的 INSERT 的自动增量列的值取决于语句执行的确切时间。如果 Tx2 的 INSERT 在 Tx1 的 INSERT 运行时执行(而不是在其开始之前或完成之后执行),则两个 INSERT 语句分配的特定自动增量值是不确定的,并且可能因运行而异。

    连续锁定模式下,InnoDB 可以避免对预先知道行数的“简单插入”语句使用表级 AUTO-INC 锁,并且仍然可以为基于语句的复制保留确定性执行和安全性。

    如果不使用二进制日志作为恢复或复制的一部分来重播 SQL 语句,则可以使用交错锁定模式来消除对表级 AUTO-INC 锁的所有使用,从而获得更大的并发性和性能,但代价是允许语句分配的自动增量编号中存在间隙,并且可能导致并发执行的语句分配的编号交错。

  • innodb_autoinc_lock_mode = 1 (“连续” 锁定模式)

    在此模式下,“批量插入”使用特殊的 AUTO-INC 表级锁,并将其保持到语句结束。这适用于所有 INSERT ... SELECTREPLACE ... SELECTLOAD DATA 语句。一次只能执行一个持有 AUTO-INC 锁的语句。如果批量插入操作的源表与目标表不同,则在对从源表中选择的第一行进行共享锁定后,将获取目标表上的 AUTO-INC 锁。如果批量插入操作的源和目标是同一个表,则在对所有选定行进行共享锁定后,将获取 AUTO-INC 锁。

    简单插入”(预先知道要插入的行数)通过在互斥体(一种轻量级锁)的控制下获取所需数量的自动增量值来避免表级 AUTO-INC 锁,该互斥体仅在分配过程中持有,而不是 一直持有到语句完成。除非另一个事务持有 AUTO-INC 锁,否则不会使用表级 AUTO-INC 锁。如果另一个事务持有 AUTO-INC 锁,“简单插入”将等待 AUTO-INC 锁,就像它是一个“批量插入”一样。

    此锁定模式可确保在存在行数未知的 INSERT 语句(以及在语句进行时分配自动增量编号)的情况下,任何“INSERT”语句分配的所有自动增量值都是连续的,并且操作对于基于语句的复制是安全的。

    简而言之,此锁定模式在确保可安全地与基于语句的复制一起使用的同时,显著提高了可伸缩性。此外,与“传统”锁定模式一样,任何给定语句分配的自动增量编号都是连续的。与使用自动增量的任何语句的“传统”模式相比,语义没有变化,但有一个重要的例外。

    例外情况是“混合模式插入”,其中用户为多行“简单插入”中某些行(但不是所有行)的 AUTO_INCREMENT 列提供显式值。对于此类插入,InnoDB 分配的自动增量值多于要插入的行数。但是,所有自动分配的值都是连续生成的(因此高于最近执行的上一个语句生成的自动增量值)。“超额”编号将丢失。

  • innodb_autoinc_lock_mode = 2 (“交错” 锁定模式)

    在此锁定模式下,任何“INSERT”语句都不使用表级 AUTO-INC 锁,并且多个语句可以同时执行。这是最快且可伸缩性最高的锁定模式,但在使用基于语句的复制或从二进制日志重播 SQL 语句的恢复方案时,它不安全

    在此锁定模式下,保证自动增量值在所有并发执行的“INSERT”语句中是唯一的并且单调递增。但是,由于多个语句可以同时生成编号(也就是说,编号的分配在语句之间交错),因此为任何给定语句插入的行生成的的值可能不是连续的。

    如果唯一执行的语句是预先知道要插入的行数的“简单插入”,则为单个语句生成的编号中不会存在间隙,但“混合模式插入”除外。但是,执行“批量插入”时,任何给定语句分配的自动增量值中都可能存在间隙。

InnoDB AUTO_INCREMENT 锁定模式的使用含义
InnoDB AUTO_INCREMENT 计数器初始化

本节介绍 InnoDB 如何初始化 AUTO_INCREMENT 计数器。

如果为 InnoDB 表指定 AUTO_INCREMENT 列,则内存中的表对象包含一个名为自动递增计数器的特殊计数器,用于为该列分配新值。

当前最大自动递增计数器值会在每次更改时写入重做日志,并在每次检查点保存到数据字典中;这使得当前最大自动递增计数器值在服务器重启后保持不变。

在正常关闭后重新启动服务器时,InnoDB 使用存储在数据字典中的当前最大自动递增值初始化内存中的自动递增计数器。

在崩溃恢复期间重新启动服务器时,InnoDB 使用存储在数据字典中的当前最大自动递增值初始化内存中的自动递增计数器,并扫描重做日志以查找自上次检查点以来写入的自动递增计数器值。如果重做日志记录的值大于内存中的计数器值,则应用重做日志记录的值。但是,如果服务器意外退出,则无法保证重复使用以前分配的自动递增值。每次由于 INSERTUPDATE 操作而更改当前最大自动递增值时,新值都会写入重做日志,但是,如果在将重做日志刷新到磁盘之前发生意外退出,则在服务器重新启动后初始化自动递增计数器时,可能会重复使用以前分配的值。

InnoDB 使用等效于 SELECT MAX(ai_col) FROM table_name FOR UPDATE 语句来初始化自动递增计数器的唯一情况是在导入没有 .cfg 元数据文件的表时。否则,如果存在,则从 .cfg 元数据文件中读取当前最大自动递增计数器值。除了计数器值初始化之外,在尝试使用 ALTER TABLE ... AUTO_INCREMENT = N 语句将计数器值设置为小于或等于持久化计数器值时,还会使用等效于 SELECT MAX(ai_col) FROM table_name 的语句来确定表的当前最大自动递增计数器值。例如,您可能会在删除一些记录后尝试将计数器值设置为较小的值。在这种情况下,必须搜索表以确保新的计数器值不小于或等于实际的当前最大计数器值。

重新启动服务器不会取消 AUTO_INCREMENT = N 表选项的效果。如果将自动递增计数器初始化为特定值,或者将自动递增计数器值更改为更大的值,则新值将在服务器重新启动后保持不变。

注意

ALTER TABLE ... AUTO_INCREMENT = N 只能将自动递增计数器值更改为大于当前最大值的值。

当前最大自动递增值是持久的,可以防止重复使用以前分配的值。

如果在初始化自动递增计数器之前,SHOW TABLE STATUS 语句检查了某个表,则 InnoDB 会打开该表,并使用存储在数据字典中的当前最大自动递增值初始化计数器值。然后,该值将存储在内存中,供以后的插入或更新操作使用。初始化计数器值使用表上的正常独占锁定读取,该读取持续到事务结束。InnoDB 在为用户指定的自动递增值大于 0 的新创建的表初始化自动递增计数器时,遵循相同的过程。

初始化自动递增计数器后,如果在插入行时未显式指定自动递增值,则 InnoDB 会隐式递增计数器并将新值分配给该列。如果插入的行显式指定了一个自动递增列值,并且该值大于当前最大计数器值,则计数器将设置为指定的值。

只要服务器运行,InnoDB 就会使用内存中的自动递增计数器。当服务器停止并重新启动时,InnoDB 会重新初始化自动递增计数器,如前所述。

auto_increment_offset 变量确定 AUTO_INCREMENT 列值的起点。默认设置为 1。

auto_increment_increment 变量控制连续列值之间的间隔。默认设置为 1。

注意

AUTO_INCREMENT 整数列的值用完时,后续的 INSERT 操作将返回重复键错误。这是 MySQL 的常规行为。