AUTO-INC锁和AUTO_INCREMENT在InnoDB中处理方式

本文深入探讨InnoDB数据库中自动递增锁的工作原理及不同配置模式的影响,帮助理解自动递增值的生成机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AUTO-INC Locks

An AUTO-INC lock is a special table-level lock taken by transactions inserting into tables with AUTO_INCREMENT columns. In the simplest case, if one transaction is inserting values into the table, any other transactions must wait to do their own inserts into that table, so that rows inserted by the first transaction receive consecutive primary key values.

The innodb_autoinc_lock_mode configuration option controls the algorithm used for auto-increment locking. It allows you to choose how to trade off between predictable sequences of auto-increment values and maximum concurrency for insert operations.

AUTO-INC锁是当向使用含有AUTO_INCREMENT列的表中插入数据时需要获取的一种特殊的表级锁 
在最简单的情况下,如果一个事务正在向表中插入值,则任何其他事务必须等待对该表执行自己的插入操作,以便第一个事务插入的行的值是连续的。 
innodb_autoinc_lock_mode配置选项控制用于自动增量锁定的算法。 它允许您选择如何在可预测的自动递增值序列和插入操作的最大并发性之间进行权衡。

AUTO_INCREMENT Handling in InnoDB

InnoDB提供了一个可配置的锁定机制,可以显着提高使用AUTO_INCREMENT列向表中添加行的SQL语句的可伸缩性和性能。 要对InnoDB表使用AUTO_INCREMENT机制,必须将AUTO_INCREMENT列定义为索引的一部分,以便可以对表执行相当于索引的SELECT MAX(ai_col)查找以获取最大列值。 通常,这是通过使列成为某些表索引的第一列来实现的。

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

  • InnoDB AUTO_INCREMENT锁定模式

  • InnoDB AUTO_INCREMENT锁定模式使用含义

  • InnoDB AUTO_INCREMENT计数器初始化

InnoDB AUTO_INCREMENT锁定模式

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

以下术语用于描述innodb_autoinc_lock_mode设置:

  • “INSERT-like” statements(类INSERT语句) 
    所有可以向表中增加行的语句,包括INSERTINSERT ... SELECTREPLACEREPLACE ... SELECT, and LOAD DATA.包括“simple-inserts”, “bulk-inserts”, and “mixed-mode” inserts.

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

  • “Bulk inserts” 
    事先不知道要插入的行数(和所需自动递增值的数量)的语句。 这包括INSERT ... SELECTREPLACE ... SELECTLOAD DATA语句,但不包括纯INSERT。 InnoDB在处理每行时一次为AUTO_INCREMENT列分配一个新值。

  • “Mixed-mode inserts” 
    这些是“Simple inserts”语句但是指定一些(但不是全部)新行的自动递增值。 示例如下,其中c1是表t1的AUTO_INCREMENT列: 
    INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');

另一种类型的“Mixed-mode inserts”是INSERT ... ON DUPLICATE KEY UPDATE,其在最坏的情况下实际上是INSERT语句随后又跟了一个UPDATE,其中AUTO_INCREMENT列的分配值不一定会在 UPDATE 阶段使用

  • innodb_autoinc_lock_mode = 0 (“traditional” lock mode)
    传统的锁定模式提供了在MySQL 5.1中引入innodb_autoinc_lock_mode配置参数之前存在的相同行为。传统的锁定模式选项用于向后兼容性,性能测试以及解决“Mixed-mode inserts”的问题,因为语义上可能存在差异。

在此锁定模式下,所有“INSERT-like”语句获得一个特殊的表级AUTO-INC锁,用于插入具有AUTO_INCREMENT列的表。此锁定通常保持到语句结束(不是事务结束),以确保为给定的INSERT语句序列以可预测和可重复的顺序分配自动递增值,并确保自动递增由任何给定语句分配的值是连续的。

  1. SESSION_A>DROP TABLE IF EXISTS t;
  2. Query OK, 0 rows affected (0.00 sec)
  3. SESSION_A>CREATE TABLE t (a bigint unsigned auto_increment primary key) ENGINE=InnoDB;
  4. Query OK, 0 rows affected (0.01 sec)
  5. SESSION_A>insert into t values(1),(3),(4),(5),(6),(7);
  6. Query OK, 6 rows affected (0.01 sec)
  7. Records: 6 Duplicates: 0 Warnings: 0
  8. SESSION_A>select * from t;
  9. +---+
  10. | a |
  11. +---+
  12. | 1 |
  13. | 3 |
  14. | 4 |
  15. | 5 |
  16. | 6 |
  17. | 7 |
  18. +---+
  19. 6 rows in set (0.00 sec)
  20. SESSION_A>select @@innodb_autoinc_lock_mode;
  21. +----------------------------+
  22. | @@innodb_autoinc_lock_mode |
  23. +----------------------------+
  24. | 0 |
  25. +----------------------------+
  26. 1 row in set (0.00 sec)
  27. A B C 三个会话事务隔离级别都是 RR
  28. SESSION_A>select @@global.tx_isolation,@@session.tx_isolation;
  29. +-----------------------+------------------------+
  30. | @@global.tx_isolation | @@session.tx_isolation |
  31. +-----------------------+------------------------+
  32. | REPEATABLE-READ | REPEATABLE-READ |
  33. +-----------------------+------------------------+
  34. SESSION_A>begin;
  35. Query OK, 0 rows affected (0.00 sec)
  36. SESSION_A>delete from t where a>4;
  37. Query OK, 3 rows affected (0.00 sec)
  38. B会话被锁,这是由于会话 A 产生的 gap lock
  39. SESSION_B>begin;
  40. Query OK, 0 rows affected (0.00 sec)
  41. SESSION_B>insert into t values(null); --注意这里因为是 null, 锁需要在内存中分配 AUTO-INCREMENT
  42. C 会话被阻塞
  43. SESSION_C>begin;
  44. Query OK, 0 rows affected (0.00 sec)
  45. SESSION_C>insert into t values(2); --这里插入2,没有 gap lock 也被锁了
  46. (mysql@localhost) [fandb]> (mysql@localhost) [fandb]> select trx_id,trx_state,trx_requested_lock_id,trx_weight,trx_mysql_thread_id,trx_query, trx_operation_state from information_schema.INNODB_TRX;
  47. +--------+-----------+-----------------------+------------+---------------------+----------------------------+-----------------------+
  48. | trx_id | trx_state | trx_requested_lock_id | trx_weight | trx_mysql_thread_id | trx_query | trx_operation_state |
  49. +--------+-----------+-----------------------+------------+---------------------+----------------------------+-----------------------+
  50. | 321912 | LOCK WAIT | 321912:701 | 3 | 7 | insert into t values(2) | setting auto-inc lock |
  51. | 321911 | LOCK WAIT | 321911:690:3:1 | 3 | 2 | insert into t values(null) | inserting |
  52. | 321906 | RUNNING | NULL | 5 | 1 | NULL | NULL |
  53. +--------+-----------+-----------------------+------------+---------------------+----------------------------+-----------------------+
  54. 3 rows in set (0.00 sec)
  55. 可以看到,SESSION_C是等待自增锁,一直处于setting auto-inc lock状态
  56. (mysql@localhost) [fandb]> select * from information_schema.INNODB_LOCKS;
  57. +----------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+------------------------+
  58. | lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
  59. +----------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+------------------------+
  60. | 321912:701 | 321912 | AUTO_INC | TABLE | `fandb`.`t` | NULL | NULL | NULL | NULL | NULL |
  61. | 321911:701 | 321911 | AUTO_INC | TABLE | `fandb`.`t` | NULL | NULL | NULL | NULL | NULL |
  62. | 321911:690:3:1 | 321911 | X | RECORD | `fandb`.`t` | PRIMARY | 690 | 3 | 1 | supremum pseudo-record |
  63. | 321906:690:3:1 | 321906 | X | RECORD | `fandb`.`t` | PRIMARY | 690 | 3 | 1 | supremum pseudo-record |
  64. +----------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+------------------------+
  65. 4 rows in set (0.00 sec)

在statement-based replication的情况下,这意味着当在从服务器上复制SQL语句时,自动增量列使用与主服务器上相同的值。多个INSERT语句的执行结果是确定性的,SLAVE再现与MASTER相同的数据。如果由多个INSERT语句生成的自动递增值交错,则两个并发INSERT语句的结果将是不确定的,并且不能使用基于语句的复制可靠地传播到从属服务器。

为了解释清楚,查看下面的例子:

  1. CREATE TABLE t1 (
  2. c1 INT(11) NOT NULL AUTO_INCREMENT,
  3. c2 VARCHAR(10) DEFAULT NULL,
  4. PRIMARY KEY (c1)
  5. ) ENGINE=InnoDB;

假设有两个事务正在运行,每个事务都将行插入到具有AUTO_INCREMENT列的表中。 一个事务正在使用插入1000行的INSERT … SELECT语句,另一个事务正在使用插入一行的“Simple inserts”语句:

  1. Tx1: INSERT INTO t1 (c2) SELECT 1000 rows from another table ...
  2. Tx2: INSERT INTO t1 (c2) VALUES ('xxx');

InnoDB不能预先得知有多少行会从TX1的select部分获取到,所以在事务进行过程中,InnoDB一次只会为AUTO_INCREMENT列分配一个值. 
通过一个表级锁的控制,保证了在同一时刻只有一个引用表t1的INSERT语句可以执行,直到整个INSERT语句结束,并且由不同语句生成自动递增数不会交错 
由Tx1 INSERT ... SELECT语句生成的自动递增值将是连续的,并且Tx2中的INSERT语句使用的(单个)自动递增值将小于或大于用于Tx1的所有那些值,具体取决于 那个语句先执行。

只要SQL语句在从二进制日志(当使用基于语句的复制或在恢复方案中)重放时以相同的顺序执行,结果将与Tx1和Tx2首次运行时的结果相同。 因此,持续至语句结束的表级锁定( table-level locks)保证了在statement-based replication中对auto-increment列的插入数据的安全性. 但是,当多个事务同时执行insert语句时,这些表级锁定会限制并发性和可伸缩性。

在前面的示例中,如果没有表级锁,则Tx2中用于INSERT的自动递增列的值取决于语句执行的确切时间。 如果Tx2的INSERT在Tx1的INSERT正在运行时(而不是在它开始之前或完成之后)执行,则由两个INSERT语句分配的特定自动递增值将是不确定的,并且可能每次运行都会得到不同的值

在连续锁定模式下,InnoDB可以避免为“Simple inserts”语句使用表级AUTO-INC锁,其中行数是预先已知的,并且仍然保留基于语句的复制的确定性执行和安全性。

如果不使用二进制日志作为恢复或复制的一部分来重放SQL语句,则可以使用interleaved lock模式来消除所有使用表级AUTO-INC锁,以实现更大的并发性和性能,其代价是由于并发的语句交错执行,同一语句生成的AUTO-INCREMENT值可能会产生GAP

  • innodb_autoinc_lock_mode = 1 (“consecutive” lock mode)
    这是默认的锁定模式.在这个模式下,“bulk inserts”仍然使用AUTO-INC表级锁,并保持到语句结束.这适用于所有INSERT ... SELECTREPLACE ... SELECTLOAD DATA语句。同一时刻只有一个语句可以持有AUTO-INC锁.

“Simple inserts”(要插入的行数事先已知)通过在mutex(轻量锁)的控制下获得所需数量的自动递增值来避免表级AUTO-INC锁, 它只在分配过程的持续时间内保持,而不是直到语句完成。 不使用表级AUTO-INC锁,除非AUTO-INC锁由另一个事务保持。 如果另一个事务保持AUTO-INC锁,则“简单插入”等待AUTO-INC锁,如同它是一个“批量插入”。

  1. SESSION_A>DROP TABLE IF EXISTS t;
  2. Query OK, 0 rows affected (0.01 sec)
  3. SESSION_A>CREATE TABLE t (a bigint unsigned auto_increment primary key) ENGINE=InnoDB;
  4. Query OK, 0 rows affected (0.01 sec)
  5. SESSION_A>insert into t values(1),(3),(4),(5),(6),(7);
  6. Query OK, 6 rows affected (0.01 sec)
  7. Records: 6 Duplicates: 0 Warnings: 0
  8. SESSION_A>select @@innodb_autoinc_lock_mode;
  9. +----------------------------+
  10. | @@innodb_autoinc_lock_mode |
  11. +----------------------------+
  12. | 1 |
  13. +----------------------------+
  14. 1 row in set (0.00 sec)
  15. SESSION_A>select * from t;
  16. +---+
  17. | a |
  18. +---+
  19. | 1 |
  20. | 3 |
  21. | 4 |
  22. | 5 |
  23. | 6 |
  24. | 7 |
  25. +---+
  26. 6 rows in set (0.00 sec)
  27. SESSION_A>begin;
  28. Query OK, 0 rows affected (0.00 sec)
  29. SESSION_A>delete from t where a>4;
  30. Query OK, 3 rows affected (0.00 sec)
  31. 会话 B, GAP LOCK 阻塞
  32. SESSION_B>begin;
  33. Query OK, 0 rows affected (0.00 sec)
  34. SESSION_B>insert into t values(null); --由于是`simple-insert``innodb_autoinc_lock_mode=1`,所以并不需要AUTO-INC表级锁
  35. 会话 C 成功插入没有阻塞
  36. SESSION_C>begin;
  37. Query OK, 0 rows affected (0.00 sec)
  38. SESSION_C>insert into t values(2); --由于它也是`simple-insert``innodb_autoinc_lock_mode=1`所以不需要获取AUTO-INC表级锁,没有阻塞成功插入
  39. Query OK, 1 row affected (0.00 sec)
  40. C会话rollback,B会话改为使用“Bulk inserts
  41. SESSION_C>rollback;
  42. Query OK, 0 rows affected (0.00 sec)
  43. SESSION_B>insert into t select null;
  44. 此时 C 会话又被阻塞了
  45. SESSION_C>begin;
  46. Query OK, 0 rows affected (0.00 sec)
  47. SESSION_C>insert into t values(2); --这验证了官方文档中的说法`If another transaction holds an AUTO-INC lock, a “simple insert” waits for the AUTO-INC lock, as if it were a “bulk insert”.`
  48. Query OK, 1 row affected (41.17 sec)

此锁定模式确保,当行数不预先知道的INSERT存在时(并且自动递增值在语句过程执行中分配)由任何“类INSERT”语句分配的所有自动递增值是连续的,并且对于基于语句的复制(statement-based replication)操作是安全的。

这种锁定模式显著地提高了可扩展性,并且保证了对于基于语句的复制(statement-based replication)的安全性.此外,与“传统”锁定模式一样,由任何给定语句分配的自动递增数字是连续的。 与使用自动递增的任何语句的“传统”模式相比,语义没有变化. 
但有一个特例:

The exception is for “mixed-mode inserts”, where the user provides explicit values for an AUTO_INCREMENT column for some, but not all, rows in a multiple-row “simple insert”. For such inserts, InnoDB allocates more auto-increment values than the number of rows to be inserted. However, all values automatically assigned are consecutively generated (and thus higher than) the auto-increment value generated by the most recently executed previous statement. “Excess” numbers are lost.

  • innodb_autoinc_lock_mode = 2 (“interleaved” lock mode)

    在这种锁定模式下,所有类INSERT(“INSERT-like” )语句都不会使用表级AUTO-INC lock,并且可以同时执行多个语句。这是最快和最可扩展的锁定模式,但是当使用基于语句的复制或恢复方案时,从二进制日志重播SQL语句时,这是不安全的。

在此锁定模式下,自动递增值保证在所有并发执行的“类INSERT”语句中是唯一且单调递增的。但是,由于多个语句可以同时生成数字(即,跨语句交叉编号),为任何给定语句插入的行生成的值可能不是连续的。

如果执行的语句是“simple inserts”,其中要插入的行数已提前知道,则除了“混合模式插入”之外,为单个语句生成的数字不会有间隙。然而,当执行“批量插入”时,在由任何给定语句分配的自动递增值中可能存在间隙。

InnoDB AUTO_INCREMENT锁定模式使用含义

  • 在复制环节中使用自增列 
    如果你在使用基于语句的复制(statement-based replication)请将innodb_autoinc_lock_mode设置为0或1,并在主从上使用相同的值。 如果使用innodb_autoinc_lock_mode = 2(“interleaved”)或主从不使用相同的锁定模式的配置,自动递增值不能保证在从机上与主机上相同。

如果使用基于行的或混合模式的复制,则所有自动增量锁定模式都是安全的,因为基于行的复制对SQL语句的执行顺序不敏感(混合模式会在遇到不安全的语句是使用基于行的复制模式)。

  • “Lost” auto-increment values and sequence gaps 
    在所有锁定模式(0,1和2)中,如果生成自动递增值的事务回滚,那些自动递增值将“丢失”。 一旦为自动增量列生成了值,无论是否完成“类似INSERT”语句以及包含事务是否回滚,都不能回滚。 这种丢失的值不被重用。 因此,存储在表的AUTO_INCREMENT列中的值可能存在间隙。

  • Specifying NULL or 0 for the AUTO_INCREMENT column 
    在所有锁定模式(0,1和2)中,如果用户在INSERT中为AUTO_INCREMENT列指定NULL或0,InnoDB会将该行视为未指定值,并为其生成新值。

  • 为AUTO_INCREMENT列分配一个负值 
    在所有锁定模式(0,1和2)中,如果您为AUTO_INCREMENT列分配了一个负值,则不会定义自动增量机制的行为。

  • 如果AUTO_INCREMENT值大于指定整数类型的最大整数 
    在所有锁定模式(0,1和2)中,如果值大于可以存储在指定整数类型中的最大整数,则不定义自动递增机制的行为。

  • Gaps in auto-increment values for “bulk inserts” 
    当innodb_autoinc_lock_mode设置为0(“traditional”)或1(“consecutive”)时,任何给定语句生成的自动递增值是连续的,没有间隙,因为表级AUTO-INC锁会持续到 语句结束,并且一次只能执行一个这样的语句。

当innodb_autoinc_lock_mode设置为2(“interleaved”)时,在“bulk inserts”生成的自动递增值中可能存在间隙,但只有在并发执行“INSERT-Like”语句时才会产生这种情况。

对于锁定模式1或2,在连续语句之间可能出现间隙,因为对于批量插入,每个语句所需的自动递增值的确切数目可能不为人所知,并且可能进行过度估计。

  1. select @@innodb_autoinc_lock_mode;
  2. +----------------------------+
  3. | @@innodb_autoinc_lock_mode |
  4. +----------------------------+
  5. | 0 |
  6. +----------------------------+
  7. DROP TABLE IF EXISTS t;
  8. CREATE TABLE t (a bigint unsigned auto_increment primary key) ENGINE=InnoDB SELECT NULL AS a;
  9. /* #1 */ INSERT INTO t SELECT NULL FROM t;
  10. /* #2 */ INSERT INTO t SELECT NULL FROM t;
  11. /* #3 */ INSERT INTO t SELECT NULL FROM t;
  12. /* #4 */ INSERT INTO t SELECT NULL FROM t;
  13. SELECT * FROM t;
  14. +----+
  15. | a |
  16. +----+
  17. | 1 |
  18. | 2 |
  19. | 3 |
  20. | 4 |
  21. | 5 |
  22. | 6 |
  23. | 7 |
  24. | 8 |
  25. | 9 |
  26. | 10 |
  27. | 11 |
  28. | 12 |
  29. | 13 |
  30. | 14 |
  31. | 15 |
  32. | 16 |
  33. +----+
  34. innodb_autoinc_lock_mode=0 INSERT语句产生的自动递增值都是连续的
  35. select @@innodb_autoinc_lock_mode;
  36. +----------------------------+
  37. | @@innodb_autoinc_lock_mode |
  38. +----------------------------+
  39. | 1 |
  40. +----------------------------+
  41. 1 row in set (0.00 sec)
  42. DROP TABLE IF EXISTS t;
  43. CREATE TABLE t (a bigint unsigned auto_increment primary key) ENGINE=InnoDB SELECT NULL AS a;
  44. /* #1 */ INSERT INTO t SELECT NULL FROM t;
  45. /* #2 */ INSERT INTO t SELECT NULL FROM t;
  46. /* #3 */ INSERT INTO t SELECT NULL FROM t;
  47. /* #4 */ INSERT INTO t SELECT NULL FROM t;
  48. SELECT * FROM t;
  49. +----+
  50. | a |
  51. +----+
  52. | 1 |
  53. | 2 |
  54. | 3 |
  55. | 4 |
  56. | 6 |
  57. | 7 |
  58. | 8 |
  59. | 9 |
  60. | 13 |
  61. | 14 |
  62. | 15 |
  63. | 16 |
  64. | 17 |
  65. | 18 |
  66. | 19 |
  67. | 20 |
  68. +----+
  69. 出现了间隙gap, 510-12都没了,下面来解释产生这种情况的原因:
  70. /* #1 */ 这是第一次INSERT,此时表中只有一行(创建表时的那一行),但是MySQL不知道有多少行.
  71. 然后MySQL Grab a chunk of auto_increment values chunk中有多少? 只有一个,即'2',将其插入表中.
  72. 没有更多的行插入,所以一切完成。
  73. /* #2 */ 这是第二次INSERT,此时表中有两行(1,2),但是MySQL不知道有多少行.
  74. MySQL Grab a chunk of auto_increment values chunk中有多少? 只有一个,即'3',将其插入表中.
  75. 还有需要插入的行,所以Grab another chunk,这次是前一次的两倍大小 chunk中有多少? 两个,'4''5'. 插入'4'.
  76. 没有更多的行插入,所以一切完成,'5'被舍弃,但是此时 AUTO_INCREMENT的下一个值是6
  77. /* #3 */这是第三次INSERT,此时表中有四行(1,2,3,4),但是MySQL不知道有多少行.
  78. - Grab a chunk of auto_increment values. How many in the chunk? One - the value '6'. Insert it (one row inserted).
  79. - Still more rows to insert. Grab another chunk, twice as big as before - two values, '7' and '8'. Insert them (three rows inserted).
  80. - Still more rows to insert. Grab another chunk, twice as big as before - four values, '9', '10', '11', '12'. Insert the '9' (four rows inserted).
  81. - No more rows to insert. Discard the left over '10', '11', and '12'.
  82. #4: Insert as many rows as there are in the table (it's eight rows, but MySQL doesn't know that.)
  83. - Grab a chunk of auto_increment values. How many in the chunk? One - the value '13'. Insert it (one row inserted).
  84. - Still more rows to insert. Grab another chunk, twice as big as before - two values, '14' and '15'. Insert them (three rows inserted).
  85. - Still more rows to insert. Grab another chunk, twice as big as before - four values, '16', '17', '18', '19'. Insert them (seven rows inserted).
  86. - Still more rows to insert. Grab another chunk, twice as big as before - eight values, '20', '21', '22', ..., '27'. Insert the '20' (eight rows inserted).
  87. - No more rows to insert. Discard the left over '21', '22', etc.
  88. 所以这就是 gap 产生的原因
  • 由“mixed-mode inserts”分配的自动递增值 
    考虑一下场景,在“mixed-mode insert”中,其中一个“simple insert”语句指定了一些(但不是全部)行的AUTO-INCREMENT值。 这样的语句在锁模式0,1和2中表现不同。例如,假设c1是表t1的AUTO_INCREMENT列,并且最近自动生成的序列号是100。
  1. mysql> CREATE TABLE t1 (
  2. -> c1 INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  3. -> c2 CHAR(1)
  4. -> ) ENGINE = INNODB;

Now, consider the following “mixed-mode insert” statement:

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

当innodb_autoinc_lock_mode=0时:

  1. mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
  2. +-----+------+
  3. | c1 | c2 |
  4. +-----+------+
  5. | 1 | a |
  6. | 101 | b |
  7. | 5 | c |
  8. | 102 | d |
  9. +-----+------+

下一个可用的auto-increment值103.因为innodb_autoinc_lock_mode=0时,auto-increment值一次只分配一个,而不是在开始时全部分配.不论是否有并发的其他类INSERT语句同时执行,都会是这样的结果

当innodb_autoinc_lock_mode=1时:

  1. mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
  2. +-----+------+
  3. | c1 | c2 |
  4. +-----+------+
  5. | 1 | a |
  6. | 101 | b |
  7. | 5 | c |
  8. | 102 | d |
  9. +-----+------+

不同于innodb_autoinc_lock_mode=0时的情况,此时下一个可用的auto-increment值105,因为auto-increment值在语句一开始就分配了,分配了四个,但是只用了俩.不论是否有并发的其他类INSERT语句同时执行,都会是这样的结果

当innodb_autoinc_lock_mode=2时:

  1. mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
  2. +-----+------+
  3. | c1 | c2 |
  4. +-----+------+
  5. | 1 | a |
  6. | x | b |
  7. | 5 | c |
  8. | y | d |
  9. +-----+------+

x和y的值是唯一的,并大于任何先前生成的行。 然而,x和y的具体值取决于通过并发执行语句生成的自动增量值的数量。

最后考虑下面的情况,当最近的 AUTO-INCREMENT 值为4时,执行下面的语句:

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

无论innodb_autoinc_lock_mode如何设置,都会报错duplicate-key error 23000 (Can't write; duplicate key in table) 
因为5已经分配给了(NULL, 'b'),所以导致插入(5, 'C')时报错

  • 在INSERT语句序列的中间修改AUTO_INCREMENT列值 
    在所有锁定模式(0,1和2)中,在INSERT语句序列中间修改AUTO_INCREMENT列值可能会导致duplicate key错误。
  1. mysql> CREATE TABLE t1 (
  2. -> c1 INT NOT NULL AUTO_INCREMENT,
  3. -> PRIMARY KEY (c1)
  4. -> ) ENGINE = InnoDB;
  5. mysql> INSERT INTO t1 VALUES(0), (0), (3); -- 0 0分配两个值1,2. 手动指定3,则此时AUTO_INCREMENT3,下一个值为4
  6. mysql> SELECT c1 FROM t1;
  7. +----+
  8. | c1 |
  9. +----+
  10. | 1 |
  11. | 2 |
  12. | 3 |
  13. +----+
  14. mysql> UPDATE t1 SET c1 = 4 WHERE c1 = 1;
  15. mysql> SELECT c1 FROM t1;
  16. +----+
  17. | c1 |
  18. +----+
  19. | 2 |
  20. | 3 |
  21. | 4 |
  22. +----+
  23. mysql> INSERT INTO t1 VALUES(0); --由于分配值为4,所以报错duplicate key
  24. ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'

InnoDB AUTO_INCREMENT计数器初始化

本章节讨论 InnoDB如何初始化AUTO_INCREMENT计数器 
如果你为一个Innodb表创建了一个AUTO_INCREMENT列,则InnoDB数据字典中的表句柄包含一个称为自动递增计数器的特殊计数器,用于为列分配新值。 此计数器仅存在于内存中,而不存储在磁盘上。

要在服务器重新启动后初始化自动递增计数器,InnoDB将在首次插入行到包含AUTO_INCREMENT列的表时执行以下语句的等效语句。

  1. SELECT MAX(ai_col) FROM table_name FOR UPDATE;

InnoDB增加语句检索的值,并将其分配给表和表的自动递增计数器。 默认情况下,值增加1.此默认值可以由auto_increment_increment配置设置覆盖。

如果表为空,InnoDB使用值1.此默认值可以由auto_increment_offset配置设置覆盖。

如果在自动递增计数器初始化前使用SHOW TABLE STATUS语句查看表, InnoDB将初始化计数器值,但不会递增该值.这个值会储存起来以备之后的插入语句使用.这个初始化过程使用了一个普通的排它锁来读取表中自增列的最大值. InnoDB遵循相同的过程来初始化新创建的表的自动递增计数器。

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

只要服务器运行,InnoDB就使用内存中自动递增计数器。当服务器停止并重新启动时,InnoDB会重新初始化每个表的计数器,以便对表进行第一次INSERT,如前所述。

服务器重新启动还会取消CREATE TABLE和ALTER TABLE语句中的AUTO_INCREMENT = N表选项的效果

<think>嗯,用户现在想了解AUTO-INC(自增)的机制,以及它如何与意向、行级表级协调。我需要先回顾之前关于意向的解释,并整合新的信息。首先,AUTO-INC是用于自增主键的,确保插入时主键的唯一性。用户可能想知道它的工作原理,还有它其他之间的交互。 根据引用[2]之前的回答,意向是表级,不会与行级冲突,但会表级冲突。AUTO-INC属于表级的一种,但可能有不同的模式。比如,MySQL的“传统模式”“连续模式”会影响的粒度并发性。需要解释这两种模式的区别,以及它们如何影响插入操作的并发。 接下来,用户提到协调的问题。要说明AUTO-INC如何与其他共存。例如,当有事务持有AUTO-INC时,是否会影响其他事务的读、写或DDL操作。可能需要参考引用[4]中的行级排他间隙的信息,看看是否存在兼容性问题。 用户还可能关心性能优化,比如如何减少AUTO-INC的冲突。比如在连续模式下使用轻量级,或者在批量插入时的优化策略。此外,结合引用[5]中的间隙,可以提到在防止幻读时,AUTO-INC间隙如何协作。 需要注意避免混淆AUTO-INC意向,虽然都是表级,但用途不同。可能需要比较它们的兼容性,比如AUTO-INC是否与意向兼容,或者与其他表级如S、X的兼容性。根据引用[3],意向表级S/X冲突,但AUTO-INC可能在这些存在时被阻塞,或者反之。 此外,用户的问题可能涉及实际应用中的问题,如自增主键的并发插入性能,或者死场景。需要举例说明不同模式下的事务如何加,以及可能导致的阻塞情况。例如,传统模式下多个插入事务会被串行化,而连续模式允许一定程度的并发。 最后,总结AUTO-INC如何与整个机制协调,强调其在保证数据一致性的同时平衡并发性能。可能需要提到参数配置,如innodb_autoinc_lock_mode,以及如何根据应用需求选择合适的模式。</think>### **AUTO-INC(自增)的机制与协调逻辑** --- #### **一、AUTO-INC的核心作用** AUTO-INC是**表级**,专门用于管理自增主键(`AUTO_INCREMENT`)的分配,确保并发插入时主键值的**唯一性****连续性**。其核心机制是通过定表的自增计数器,防止多个事务同时插入导致主键冲突[^2][^5]。 --- #### **二、AUTO-INC的工作模式** MySQL提供两种自增模式(通过参数`innodb_autoinc_lock_mode`控制): 1. **传统模式(0)**: - 事务插入前需获取表级AUTO-INC,**完全串行化**自增值分配。 - **优点**:严格保证主键连续。 - **缺点**:并发性能低,高并发插入场景易成瓶颈[^2][^5]。 2. **连续模式(1/2)**: - **模式1(默认)**:对批量插入(如`INSERT ... SELECT`)使用AUTO-INC,单行插入使用轻量级- **模式2**:所有插入均使用轻量级,主键连续但不严格递增(仅在无并发插入时连续)。 - **优点**:提升并发性能。 - **缺点**:主键可能不连续(如事务回滚或并发插入)[^5]。 --- #### **三、AUTO-INC与其他的协调** ##### **1. 与意向的关系** - **兼容性**: - AUTO-INC是**表级独占**,与**意向(IS/IX)不兼容**。 - 例如:事务A持有AUTO-INC时,事务B无法对表加IX(插入新行需IX)[^2][^3]。 - **冲突场景**: ```sql -- 事务A(批量插入,需AUTO-INC) INSERT INTO users (name) SELECT name FROM temp_users; -- 事务B(尝试插入单行,需IX) INSERT INTO users (name) VALUES ('Alice'); -- 被阻塞直到事务A释放AUTO-INC ``` ##### **2. 与行级的协调** - **插入意向(Insert Intention Locks)**: - 行级的一种,用于标记插入位置的间隙。 - **兼容性**:AUTO-INC与插入意向不冲突,允许并发插入不同间隙。 - **示例**: ```sql -- 事务A(持有AUTO-INC插入id=10) INSERT INTO users (id, name) VALUES (10, 'Bob'); -- 事务B(插入id=5,需在间隙(0,10)加插入意向) INSERT INTO users (id, name) VALUES (5, 'Alice'); -- 允许执行 ``` ##### **3. 与表级的冲突** - **表级排他(X)**: - AUTO-INC与表级X互斥。若事务持有AUTO-INC,其他事务无法对表加X(如DDL操作)。 - **典型冲突**: ```sql -- 事务A(批量插入) INSERT INTO users (name) SELECT name FROM temp_users; -- 持有AUTO-INC -- 事务B(尝试修改表结构) ALTER TABLE users ADD COLUMN age INT; -- 被阻塞,需等待AUTO-INC释放 ``` --- #### **四、优化与典型问题** ##### **1. 性能优化策略** - **使用连续模式**:设置`innodb_autoinc_lock_mode=2`,牺牲主键连续性换取高并发性能。 - **避免长事务**:减少AUTO-INC持有时间,如拆分批量插入为小批次。 ##### **2. 典型死场景** **场景**: - 事务A持有AUTO-INC并尝试获取某行的X- 事务B持有该行的X并尝试插入新行(需AUTO-INC)。 **解决**: - 调整事务顺序,或使用`innodb_autoinc_lock_mode=2`减少竞争。 --- ### **总结** AUTO-INC通过**表级独占**机制协调自增主键分配,与意向、行级的交互规则如下: 1. **与意向互斥**:AUTO-INC阻塞表级IX的获取。 2. **与插入意向兼容**:允许并发插入不同间隙。 3. **与表级X互斥**:影响DDL操作的执行效率。 其设计在**数据一致性**与**并发性能**之间权衡,需根据业务场景选择合适的模式[^2][^5]。 --- ### **相关问题** 1. 如何通过`innodb_autoinc_lock_mode`参数优化高并发插入场景? 2. AUTO-INC在事务回滚时如何处理已分配的自增值? 3. 插入意向与间隙(Gap Lock)的协同机制是什么? --- **引用标注** [^1]: 自增是表级,用于管理自增主键的分配。 : 自增与意向的兼容性直接影响并发插入性能。 [^3]: 插入意向允许不同事务在非冲突间隙并发插入。 [^4]: 表级XAUTO-INC互斥,导致DDL操作阻塞。 : 自增模式的选择需权衡主键连续性与并发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值