文件首頁
MySQL 8.4 參考手冊
相關文件 下載本手冊
PDF (美式信紙) - 39.9Mb
PDF (A4) - 40.0Mb
Man Pages (TGZ) - 258.5Kb
Man Pages (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 參考手冊  /  ...  /  InnoDB 中的 AUTO_INCREMENT 處理

17.6.1.6 InnoDB 中的 AUTO_INCREMENT 處理

InnoDB 提供可設定的鎖定機制,可以顯著提高將列新增至具有 AUTO_INCREMENT 資料表之 SQL 陳述式的可擴展性和效能。若要將 AUTO_INCREMENT 機制與 InnoDB 資料表搭配使用,必須將 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 語句在從二進位日誌重新播放時(當使用基於語句的複寫時或在復原情境中)以相同的順序執行,結果就會與 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_autoinc_lock_mode 設定為 0 或 1,並在來源及其複本上使用相同的值。如果您使用 innodb_autoinc_lock_mode = 2 (「交錯」) 或來源和複本未使用相同鎖定模式的設定,則無法保證複本上的自動遞增值與來源上的值相同。

    如果您使用基於列的複寫或混合格式複寫,則所有自動遞增鎖定模式都是安全的,因為基於列的複寫對 SQL 語句的執行順序不敏感(且混合格式針對任何對於基於語句的複寫不安全的語句,會使用基於列的複寫)。

  • 遺失的自動遞增值和序列間隙

    在所有鎖定模式(0、1 和 2)中,如果產生自動遞增值的交易回滾,則這些自動遞增值會遺失。一旦為自動遞增欄位產生值,就無法回滾,無論「INSERT 類型的」語句是否完成,以及是否回滾包含的交易。此類遺失的值不會重複使用。因此,在表格的 AUTO_INCREMENT 欄位中儲存的值可能會有間隙。

  • AUTO_INCREMENT 欄位指定 NULL 或 0

    在所有鎖定模式(0、1 和 2)中,如果使用者在 INSERT 中為 AUTO_INCREMENT 欄位指定 NULL 或 0,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 (「交錯」) 時,大量插入產生的自動遞增值中可能會有間隙,但僅在有同時執行的「INSERT 類型的」語句時才會發生。

    對於鎖定模式 1 或 2,連續語句之間可能會出現間隙,因為對於大量插入,每個語句所需的確切自動遞增值數量可能未知,而且可能出現高估的情況。

  • 混合模式插入」指派的自動遞增值

    考慮「混合模式插入」,其中「簡單插入」指定某些(但不是所有)結果列的自動遞增值。此類語句在鎖定模式 0、1 和 2 中的行為有所不同。例如,假設 c1 是表格 t1AUTO_INCREMENT 欄位,且最近一次自動產生的序列號為 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 類型的」語句(任何類型),此結果都成立。

    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 類型的」語句(任何類型),此結果都成立。

    innodb_autoinc_lock_mode 設定為 2 (「交錯」) 時,四個新列為

    mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
    +-----+------+
    | c1  | c2   |
    +-----+------+
    |   1 | a    |
    |   x | b    |
    |   5 | c    |
    |   y | d    |
    +-----+------+

    xy 的值是唯一的,且大於任何先前產生的列。然而,xy 的特定值取決於同時執行的語句所產生的自動遞增值數量。

    最後,考慮在最近產生的序列號為 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),因為會為列 (NULL, 'b') 分配 101,並且插入列 (101, 'c') 失敗。

  • INSERT 語句序列的中間修改 AUTO_INCREMENT 欄位值

    如果您將 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 計數器初始化

本節說明 InnoDB 如何初始化 AUTO_INCREMENT 計數器。

如果您為 InnoDB 表格指定 AUTO_INCREMENT 欄位,則記憶體中的表格物件會包含一個特殊的計數器,稱為自動遞增計數器,用於為該欄位指派新值。

每次變更時,目前的最大自動遞增計數器值都會寫入重做日誌,並在每個檢查點儲存到資料字典中;這會使目前的最大自動遞增計數器值在伺服器重新啟動後保持不變。

在正常關閉後重新啟動伺服器時,InnoDB 會使用儲存在資料字典中的目前最大自動遞增值初始化記憶體中的自動遞增計數器。

在當機復原期間重新啟動伺服器時,InnoDB 會使用儲存在資料字典中的目前最大自動遞增值初始化記憶體中的自動遞增計數器,並掃描重做日誌中自上次檢查點以來寫入的自動遞增計數器值。如果重做記錄的值大於記憶體中的計數器值,則會套用重做記錄的值。然而,在伺服器意外退出的情況下,無法保證重複使用先前分配的自動遞增值。每次由於 INSERTUPDATE 作業而變更目前的最大自動遞增值時,新值都會寫入重做日誌,但如果意外退出發生在重做日誌刷新到磁碟之前,則在伺服器重新啟動後初始化自動遞增計數器時,可能會重複使用先前分配的值。

只有在InnoDB匯入沒有 .cfg 中繼資料檔案的資料表時,才會使用相當於 SELECT MAX(ai_col) FROM table_name FOR UPDATE 陳述式來初始化自動遞增計數器。否則,如果存在 .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 的一般行為。