Mysql

兩個事務可以並行鎖定同一個表中的行嗎?

  • February 18, 2022

我試圖掌握 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 COMMITTEDREPEATABLE READSERIALIZABLEREPEATABLE 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關閉

MySQL Workbench - 查詢選項 - 自動送出事務關閉

第一個標籤程式碼

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

引用自:https://dba.stackexchange.com/questions/286159