簡體   English   中英

了解 MySQL InnoDB 鎖

[英]Understanding MySQL InnoDB locks

我對 MySQL InnoDB 鎖的工作方式感到困惑。

為了更好地理解它,我進行了以下實驗。 我已經使用 Spring boot 和 JPA 在下表中插入/更新記錄(盡管這個問題與 Spring boot 或 JPA 無關)

表名: test

列名 類型 約束
ID 大整數 PK人工智能
姓名 瓦爾查爾
整數
br_id 整數

Innodb 鎖等待超時: 50 秒

存儲庫: ITestRepository.java

@Modifying
@Query(value = "update test set val=val+1 where val>:val and br_id=:brId", nativeQuery = true) // Just a random update query
public void updateVal(Integer val, Integer brId);

服務: TestService.java

@org.springframework.transaction.annotation.Transactional
public void saveMany(final int brId) { // brId is always passed as 1
    for (int i = 0; i < 200; i++) {
        this.testRepository.updateVal(i, brId); // ======> LINE: 1
        this.testRepository.save(new TestEntity(String.valueOf(i), i, brId)); // ======> LINE: 2
        try {
            Thread.sleep(2000);
        } catch (final InterruptedException e) {
            e.printStackTrace();
        }
    }
}

@org.springframework.transaction.annotation.Transactional
public void saveOne(final int val, final int brId) { // brId is always passed as 2
    this.testRepository.updateVal(val, brId); // ======> LINE: 3
    this.testRepository.save(new TestEntity(String.valueOf(val), val, brId)); // ======> LINE: 4
}
  • 注意 1:在執行以下任何情況之前,表總是被截斷。
  • 注 2:有意使上述update查詢不會更新任何內容。

案例 1:只觸發一次saveMany方法

在這種情況下,一切正常,一旦事務完成且沒有任何錯誤,就會插入 200 行。

案例2:觸發saveMany第一,然后saveOne一次

在這種情況下,如果我們觸發saveOne ,當saveMany正在執行時, saveOne將在 50 秒后因Lock wait timeout異常而失敗,並且一旦循環結束, saveMany將成功完成。

案例 3:評論LINE: 1 & LINE: 3或評論LINE: 2 & LINE: 4並先觸發saveMany ,然后再saveOne

在這種情況下,如果我們觸發saveOne ,當saveMany正在執行時,一切都會正常工作,兩個方法都會成功完成,沒有任何異常。

案例4:評論LINE: 1並觸發saveMany先,然后saveOne一次

在這種情況下,如果我們觸發saveOne ,當saveMany正在執行時, saveOne將在LINE: 3上失敗,並且更新操作的lock wait timeout異常。

案例5:評論LINE: 2並先觸發saveMany ,然后saveOne一次

在這種情況下,如果我們觸發saveOne ,當saveMany正在執行時, saveOne將在LINE: 4上失敗,並且插入操作的lock wait timeout異常。

從上面的案例這里是我的推導:

  1. 並行insertsupdates不會鎖定表
  2. 並行insertupdate會鎖表(先觸發哪個操作先獲取鎖)

我無法理解上述兩種推導是如何工作的。 我的意思是從 MySQL 文檔中,他們聲明整個表永遠不會在 InnoDB 中鎖定,並且只有在執行insertsupdates時才鎖定行。 正如你在上面的例子中看到的,兩種方法的br_id總是不同的,因此updates是在不同的行集上執行的,那么為什么會引發lock wait timeout異常? 此外,並行insertsupdates不會導致任何問題,如何以及為什么?

編輯 1:

如果br_id沒有被索引,那么它的工作方式和上面的情況一樣,但是如果br_id被索引,那么Deadlock found when trying to get lock; try restarting transaction Deadlock found when trying to get lock; try restarting transaction異常而執行被立即拋出saveOne方法並行saveMany方法。

update test
    set val = ...
    where val > ...
      and br_id = ...

由於相關列上沒有索引,所有行都被鎖定。

最好有

INDEX(br_id)

或者

INDEX(br_id, val)

后者更適合定位要修改的行,但缺點是需要更新索引的 BTree。

請注意,某些“鎖”是“間隙鎖”。 這涉及鎖定索引值之間的差距。 這有時會引起意外。 (我不知道這是否與此更新有關。)

查看鎖的一種工具是SHOW ENGINE INNODB STATUS; (由於那里的信息是暫時的,它可能無法顯示您需要的信息。)

BEGIN;
UPDATE ...
((spend lots of time before COMMIT))
COMMIT;

這將延遲或死鎖需要相同行鎖的任何競爭連接。

幾乎總是 InnoDB 可以看到簡單地停止(最多innodb_lock_wait_timeout秒)以獲得必要的鎖。

InnoDB總是可以發現死鎖,並且會ROLLBACK其中一個事務,同時讓另一個完成。

有幾種情況(尤其是“間隙鎖定”,其中 InnoDB 在可能沒有死鎖的情況下保守地聲明一個死鎖。(我不知道這是否是您的情況。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM