兩個事務可以並行鎖定同一個表中的行嗎?
我試圖掌握 MySQL 鎖的概念,並在文件中遇到了這一部分:https ://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html :
鎖定讀取、UPDATE 或 DELETE 通常會在 SQL 語句處理過程中掃描的每個索引記錄上設置記錄鎖。語句中是否存在將排除該行的 WHERE 條件並不重要。InnoDB 不記得確切的 WHERE 條件,而只知道掃描了哪些索引範圍。
這使我得出結論,兩個事務永遠不能並行鎖定同一個表中的行。讓我在下面的例子中解釋它:
假設我有下表:
CREATE TABLE t1 (id INT, PRIMARY KEY (id)) ENGINE = InnoDB;
此外,假設我插入了 7 行。這是主鍵的平衡樹:
現在假設我們用 id 鎖定行
3
:SELECT * FROM t1 WHERE id=3 FOR UPDATE
從上面的段落
鎖定讀取
$$ .. $$在 SQL 語句處理過程中掃描的每條索引記錄上設置記錄鎖。
我猜想 row 上有一個排他鎖
3,5,10
。是對的嗎?如果是這樣的話,那麼
SELECT * FROM t1 WHERE id=100 FOR UPDATE
會嘗試在行上設置排他鎖
10
,因為它會在掃描過程中找到100
。但這意味著,它必須等待其他事務完成。這意味著,任何鎖定表中行的事務
t1
將始終首先鎖定具有 id 的行10
。因此,兩個事務不可能並行鎖定兩個不同的行。我的問題是:
- 這是真的嗎?
- 我如何才能真正看到平衡索引樹,以便進行一些本地測試?
您的範例忘記了一條重要資訊,即同一行數據可以按不同標準多次索引。如果 B 樹中存在第二個索引,例如索引按
id
降序排序,那麼您將能夠同時訪問具有值的行100
(這將是您的第二個索引的根節點)。現在假設按行不是指表行,而是指單個索引中的特定行位置。那麼是的,你上面的例子是正確的,當只在同一個索引中討論時。
請注意,這個答案有點高級,只是為了解釋您問題上下文中的概念,我不能確切地說 MySQL 具體可以在幕後做什麼。
本質上,由於 InnodDB 引擎的預設事務隔離級別,沒有.
InnoDB 提供 SQL:1992 標準描述的所有四個事務隔離級別:
READ UNCOMMITTED
、、、和。InnoDB 的預設隔離級別是.READ COMMITTED
REPEATABLE READ
SERIALIZABLE
REPEATABLE READ
參考: 15.7.2.1 事務隔離級別(Dev MySQL 8.0)
我設置了以下環境:
創建數據庫、添加表、插入數據
use Q286159; CREATE TABLE t1 (id INT, PRIMARY KEY (id)) ENGINE = InnoDB; insert into t1 (id) values (3),(5),(7),(10),(100),(50),(110);
複製
然後我繼續並在一個選項卡中選擇了數據,並在另一個選項卡中使用相同的行更新了完全相同的行
id
。確保您已
Auto-Commit Transactions
關閉第一個標籤程式碼
set autocommit = 0; use Q286159; start transaction; BEGIN; select * from t1 where id = 10 for update;
第二個標籤程式碼
update t1 set id = 9 where id = 10;
結果
09:32:16 update t1ni set id = 9 where id = 10 1 row(s) affected Rows matched: 1 Changed: 1 Warnings: 0 0.000 sec
好的,這不是您所期望的,而是因為可以預期預設的事務隔離級別。
讓我們看一下問答,它稍微解釋了幕後發生的事情。
評論接受的答案:
我嘗試使用 BEGIN 進行阻塞讀取;程序 1 中的 SELECT … FOR UPDATE 和程序 2 中同一張表的 SELECT *。它似乎沒有阻塞,而是立即返回事務開始之前存在的數據。那是對的嗎?
對該評論的回應:
@you786是的,這是正確的,因為 InnoDB 的預設事務隔離級別是 REPEATABLE READ,無論有多少 SELECT * 查詢使用實時 SELECT …FOR UPDATE 執行
回答您的問題
我猜想第 3、5、10 行有一個排他鎖。是對的嗎? (這是真的嗎?)
不,因為上面的解釋和 MySQL 文件中提供的資訊:
對於具有唯一搜尋條件的唯一索引,InnoDB只鎖定找到的索引記錄,而不鎖定它之前的間隙。
即使我選擇了
id
將被鎖定以直接更新相同 id (10) 的記錄,該記錄也只被鎖定了幾分之一秒。更新成功。進一步的選擇是可能的。您實際上必須將隔離級別切換為已送出讀,然後嘗試更新行以產生某種形式的鎖定。
第 1 節
set autocommit = off; set session transaction isolation level read committed; start transaction; select * from t1 where id = 10 for update;
第 2 節
set autocommit = off; set session transaction isolation level read committed; start transaction; update t1 set id = 9 where id = 10;
返回第 1 節
update t1 set id = 10 where id = 9;
不更新,因為數據(記錄)被Session 2中的更新鎖定,但尚未送出。對
select ... for update
會話 2 沒有任何影響update...
。只有
commit;
在第 2 次會話update
中顯式後,第 1次會話才會成功。我如何才能真正看到平衡索引樹,以便進行一些本地測試?
如果您查看此答案,則沒有(合理的)查看實際 b-tree 索引的方法:
如何查看 MySQL 的 InnoDB 生成的 B-tree 索引結構?(堆棧溢出)
沒有官方工具可以查看 InnoDB B-tree 索引結構的內部結構。
有一組實驗性工具可以檢查 InnoDB 頁面的內部結構,但它不會以人類可讀的方式顯示 B-tree。https://github.com/jeremycole/innodb_ruby