當相同的查詢在不同時間產生不同的資料列集合時,會在交易內發生所謂的幻影問題。例如,如果執行兩次 SELECT
,但第二次傳回第一次未傳回的資料列,則該資料列為「幻影」資料列。
假設 child
資料表的 id
欄位上有索引,且您想要讀取並鎖定資料表中識別碼值大於 100 的所有資料列,以便稍後更新選取資料列中的某些欄位
SELECT * FROM child WHERE id > 100 FOR UPDATE;
該查詢會從 id
大於 100 的第一筆記錄開始掃描索引。假設資料表包含 id
值為 90 和 102 的資料列。如果掃描範圍中索引記錄上設定的鎖定沒有鎖定間隙(在此範例中,為 90 和 102 之間的間隙)中的插入,則另一個工作階段可以使用 id
為 101 插入新的資料列到資料表中。如果您在相同交易內執行相同的 SELECT
,您將會在查詢傳回的結果集中看到 id
為 101 的新資料列(「幻影」)。如果我們將一組資料列視為資料項目,則新的幻影子系會違反交易的隔離原則,即交易應該能夠執行,以便其讀取的資料在交易期間不會變更。
為了防止幻影,InnoDB
使用一種稱為下一個索引鍵鎖定的演算法,該演算法結合了索引列鎖定與間隙鎖定。InnoDB
以這種方式執行資料列層級鎖定:當搜尋或掃描資料表索引時,會在遇到的索引記錄上設定共用鎖定或獨佔鎖定。因此,資料列層級鎖定實際上是索引記錄鎖定。此外,索引記錄上的下一個索引鍵鎖定也會影響索引記錄之前的「間隙」。也就是說,下一個索引鍵鎖定是一個索引記錄鎖定,加上索引記錄之前間隙的間隙鎖定。如果一個工作階段在索引中的記錄 R
上具有共用鎖定或獨佔鎖定,則另一個工作階段無法在索引順序中 R
之前的間隙中插入新的索引記錄。
當 InnoDB
掃描索引時,它也可以鎖定索引中最後一筆記錄之後的間隙。這正是前面範例中發生的情況:為了防止任何插入到 id
大於 100 的資料表中,InnoDB
設定的鎖定會包含 id
值 102 之後間隙的鎖定。
您可以使用下鍵鎖定 (next-key locking) 來在您的應用程式中實作唯一性檢查:如果您以共享模式讀取您的資料,且沒有看到您將要插入的列的重複項,那麼您可以安全地插入您的列,並且知道在讀取期間於您列的後繼列上設定的下鍵鎖定會防止其他人同時插入您列的重複項。因此,下鍵鎖定使您能夠「鎖定」表格中某個東西不存在的事實。
如第 17.7.1 節「InnoDB 鎖定」中所述,可以停用間隙鎖定 (gap locking)。這可能會導致幻讀問題,因為當停用間隙鎖定時,其他會話可能會在新間隙中插入新列。