文件首頁
MySQL 8.4 參考手冊
相關文件 下載本手冊
PDF (US Ltr) - 39.9Mb
PDF (A4) - 40.0Mb
Man Pages (TGZ) - 258.5Kb
Man Pages (Zip) - 365.5Kb
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 查詢設定的所有鎖定。

注意

只有在停用自動提交 (autocommit) 時,才能使用鎖定讀取。停用方式為使用 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 |
+---+