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


17.7.2.4 鎖定讀取

如果您查詢資料,然後在同一個交易中插入或更新相關資料,則常規的 SELECT 陳述式提供的保護不足。其他交易可以更新或刪除您剛查詢的相同列。InnoDB 支援兩種提供額外安全性的鎖定讀取類型。

  • SELECT ... FOR SHARE

    在任何讀取的列上設定共用模式鎖定。其他工作階段可以讀取這些列,但在您的交易提交之前無法修改它們。如果這些列中的任何一列被另一個尚未提交的交易更改,您的查詢會等待該交易結束,然後使用最新的值。

    注意

    SELECT ... FOR SHARESELECT ... LOCK IN SHARE MODE 的替代方案,但 LOCK IN SHARE MODE 仍可向後相容。這些陳述式是等效的。但是,FOR SHARE 支援 OF table_nameNOWAITSKIP LOCKED 選項。請參閱使用 NOWAIT 和 SKIP LOCKED 的鎖定讀取並行性

    SELECT ... FOR SHARE 需要 SELECT 權限。

    SELECT ... FOR SHARE 陳述式不會在 MySQL 授權表上取得讀取鎖定。如需詳細資訊,請參閱授權表並行性

  • SELECT ... FOR UPDATE

    對於搜尋遇到的索引記錄,會鎖定這些列和任何相關的索引項目,就像您為這些列發出 UPDATE 陳述式一樣。其他交易會被阻止更新這些列、執行 SELECT ... FOR SHARE 或在某些交易隔離層級中讀取資料。一致性讀取會忽略讀取視圖中存在的記錄上設定的任何鎖定。(無法鎖定記錄的舊版本;它們會透過在記錄的記憶體內副本上套用復原日誌來重建。)

    SELECT ... FOR UPDATE 需要 SELECT 權限,以及至少 DELETELOCK TABLESUPDATE 其中一項權限。

這些子句主要用於處理樹狀或圖狀結構的資料,無論是在單一表格或分散於多個表格。您從一個地方遍歷邊緣或樹狀分支到另一個地方,同時保留返回並更改任何這些「指標」值的權利。

當交易提交或回滾時,所有由 FOR SHAREFOR UPDATE 查詢設定的鎖定都會被釋放。

注意

只有在禁用自動提交時才能進行鎖定讀取(透過使用 START TRANSACTION 開始交易,或將 autocommit 設定為 0)。

除非在子查詢中也指定了鎖定讀取子句,否則外部語句中的鎖定讀取子句不會鎖定巢狀子查詢中表格的資料列。例如,以下語句不會鎖定表格 t2 中的資料列。

SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;

若要鎖定表格 t2 中的資料列,請在子查詢中新增鎖定讀取子句。

SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;
鎖定讀取範例

假設您想要在表格 child 中插入新的資料列,並確保子資料列在表格 parent 中有父資料列。您的應用程式程式碼可以在此操作序列中確保參照完整性。

首先,使用一致性讀取來查詢表格 PARENT 並驗證父資料列是否存在。您可以安全地將子資料列插入表格 CHILD 嗎?不行,因為在您的 SELECTINSERT 之間,其他會話可能會刪除父資料列,而您卻不知道。

為了避免這個潛在問題,請使用 FOR SHARE 執行 SELECT

SELECT * FROM parent WHERE NAME = 'Jones' FOR SHARE;

FOR SHARE 查詢傳回父項 'Jones' 之後,您可以安全地將子記錄新增至 CHILD 表格並提交交易。任何嘗試在 PARENT 表格中適用的資料列上取得獨佔鎖定的交易都會等待您完成,也就是說,直到所有表格中的資料都處於一致的狀態。

另一個範例,請考慮表格 CHILD_CODES 中的整數計數器欄位,用於為新增至表格 CHILD 的每個子項分配唯一的識別碼。請勿使用一致性讀取或共享模式讀取來讀取計數器的目前值,因為資料庫的兩個使用者可能會看到相同的計數器值,而且如果兩個交易嘗試將具有相同識別碼的資料列新增至 CHILD 表格,則會發生重複鍵錯誤。

在這裡,FOR SHARE 不是一個好的解決方案,因為如果兩個使用者同時讀取計數器,則至少其中一個使用者在嘗試更新計數器時最終會陷入死鎖。

若要實作讀取和遞增計數器,請先使用 FOR UPDATE 執行計數器的鎖定讀取,然後遞增計數器。例如:

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;

SELECT ... FOR UPDATE 讀取最新的可用資料,並在讀取的每個資料列上設定獨佔鎖定。因此,它會設定與搜尋過的 SQL UPDATE 在資料列上設定的鎖定相同的鎖定。

上述描述僅是 SELECT ... FOR UPDATE 如何運作的範例。在 MySQL 中,產生唯一識別碼的特定任務實際上可以使用對表格的單一存取來完成。

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();

SELECT 語句僅檢索識別碼資訊(特定於目前的連線)。它不會存取任何表格。

使用 NOWAIT 和 SKIP LOCKED 的鎖定讀取並行性

如果資料列被交易鎖定,則要求相同鎖定資料列的 SELECT ... FOR UPDATESELECT ... FOR SHARE 交易必須等待,直到封鎖交易釋放資料列鎖定。這種行為可防止交易更新或刪除其他交易查詢以進行更新的資料列。但是,如果您希望查詢在請求的資料列被鎖定時立即傳回,或者如果從結果集中排除鎖定的資料列是可以接受的,則無需等待資料列鎖定被釋放。

為了避免等待其他交易釋放資料列鎖定,NOWAITSKIP LOCKED 選項可以與 SELECT ... FOR UPDATESELECT ... FOR SHARE 鎖定讀取語句一起使用。

  • NOWAIT

    使用 NOWAIT 的鎖定讀取永遠不會等待取得資料列鎖定。查詢會立即執行,如果請求的資料列被鎖定,則會失敗並出現錯誤。

  • SKIP LOCKED

    使用 SKIP LOCKED 的鎖定讀取永遠不會等待取得資料列鎖定。查詢會立即執行,並從結果集中移除鎖定的資料列。

    注意

    跳過鎖定資料列的查詢會傳回不一致的資料視圖。SKIP LOCKED 因此不適用於一般交易工作。但是,它可用於避免多個會話存取相同的類佇列表格時發生鎖定爭用。

NOWAITSKIP LOCKED 僅適用於資料列層級鎖定。

使用 NOWAITSKIP LOCKED 的語句對於基於語句的複製是不安全的。

以下範例示範 NOWAITSKIP LOCKED。會話 1 開始一個在單一記錄上取得資料列鎖定的交易。會話 2 嘗試使用 NOWAIT 選項對同一記錄執行鎖定讀取。由於請求的資料列被會話 1 鎖定,鎖定讀取會立即傳回錯誤。在會話 3 中,使用 SKIP LOCKED 的鎖定讀取會傳回請求的資料列,但會話 1 鎖定的資料列除外。

# Session 1:

mysql> CREATE TABLE t (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;

mysql> INSERT INTO t (i) VALUES(1),(2),(3);

mysql> START TRANSACTION;

mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE;
+---+
| i |
+---+
| 2 |
+---+

# Session 2:

mysql> START TRANSACTION;

mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE NOWAIT;
ERROR 3572 (HY000): Do not wait for lock.

# Session 3:

mysql> START TRANSACTION;

mysql> SELECT * FROM t FOR UPDATE SKIP LOCKED;
+---+
| i |
+---+
| 1 |
| 3 |
+---+