简体   繁体   English

了解 MySQL InnoDB 锁

[英]Understanding MySQL InnoDB locks

I am confused on how MySQL InnoDB locks work.我对 MySQL InnoDB 锁的工作方式感到困惑。

To understand it in better way, I have conducted below experiment.为了更好地理解它,我进行了以下实验。 I have used Spring boot and JPA to insert/update records in below table (though this question is not related to Spring boot or JPA) .我已经使用 Spring boot 和 JPA 在下表中插入/更新记录(尽管这个问题与 Spring boot 或 JPA 无关)

Table name: test表名: test

Column name列名 Type类型 Constraint约束
id ID Bigint大整数 PK AI PK人工智能
name姓名 Varchar瓦尔查尔
val Int整数
br_id br_id Int整数

Innodb lock wait timeout: 50 sec Innodb 锁等待超时: 50 秒

Repository: ITestRepository.java存储库: 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);

Service: TestService.java服务: 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
}
  • NOTE 1: Table is always truncated before executing any of the below cases.注意 1:在执行以下任何情况之前,表总是被截断。
  • NOTE 2: It is intentional that above update query will not update anything.注 2:有意使上述update查询不会更新任何内容。

Case 1: Triggering only saveMany method once案例 1:只触发一次saveMany方法

In this case, everything works fine, and 200 rows gets inserted once the transaction is completed without any error.在这种情况下,一切正常,一旦事务完成且没有任何错误,就会插入 200 行。

Case 2: Triggering saveMany first, and then saveOne once案例2:触发saveMany第一,然后saveOne一次

In this case, while saveMany is being executed if we trigger saveOne , saveOne will fail with Lock wait timeout exception after 50 sec, and saveMany will successfully complete once the loop is over.在这种情况下,如果我们触发saveOne ,当saveMany正在执行时, saveOne将在 50 秒后因Lock wait timeout异常而失败,并且一旦循环结束, saveMany将成功完成。

Case 3: Commenting LINE: 1 & LINE: 3 OR commenting LINE: 2 & LINE: 4 and Triggering saveMany first, and then saveOne once案例 3:评论LINE: 1 & LINE: 3或评论LINE: 2 & LINE: 4并先触发saveMany ,然后再saveOne

In this case, while saveMany is being executed if we trigger saveOne , everything will work fine and both the methods will complete successfully without any exception.在这种情况下,如果我们触发saveOne ,当saveMany正在执行时,一切都会正常工作,两个方法都会成功完成,没有任何异常。

Case 4: Commenting LINE: 1 and Triggering saveMany first, and then saveOne once案例4:评论LINE: 1并触发saveMany先,然后saveOne一次

In this case, while saveMany is being executed if we trigger saveOne , saveOne will fail on LINE: 3 with lock wait timeout exception for update operation.在这种情况下,如果我们触发saveOne ,当saveMany正在执行时, saveOne将在LINE: 3上失败,并且更新操作的lock wait timeout异常。

Case 5: Commenting LINE: 2 and Triggering saveMany first, and then saveOne once案例5:评论LINE: 2并先触发saveMany ,然后saveOne一次

In this case, while saveMany is being executed if we trigger saveOne , saveOne will fail on LINE: 4 with lock wait timeout exception for insert operation.在这种情况下,如果我们触发saveOne ,当saveMany正在执行时, saveOne将在LINE: 4上失败,并且插入操作的lock wait timeout异常。

From above cases here are my derivations:从上面的案例这里是我的推导:

  1. Parallel inserts or updates does not lock tables并行insertsupdates不会锁定表
  2. Parallel insert and update will lock the table (whichever operation is triggered first will acquire lock first)并行insertupdate会锁表(先触发哪个操作先获取锁)

I am not able to understand how above two derivations work.我无法理解上述两种推导是如何工作的。 I mean from the MySQL documentation, they state that whole table is never locked in InnoDB, and only the rows are locked while performing inserts or updates .我的意思是从 MySQL 文档中,他们声明整个表永远不会在 InnoDB 中锁定,并且只有在执行insertsupdates时才锁定行。 As you can see in above cases, br_id is always different for both the methods, and thus updates are being performed on different set of rows, then also why lock wait timeout exception is raised?正如你在上面的例子中看到的,两种方法的br_id总是不同的,因此updates是在不同的行集上执行的,那么为什么会引发lock wait timeout异常? Also, parallel inserts or updates are not causing any issue, how and why?此外,并行insertsupdates不会导致任何问题,如何以及为什么?

EDIT 1:编辑 1:

If br_id is not indexed, then it works like stated in above cases, but if br_id is indexed, then Deadlock found when trying to get lock; try restarting transaction如果br_id没有被索引,那么它的工作方式和上面的情况一样,但是如果br_id被索引,那么Deadlock found when trying to get lock; try restarting transaction Deadlock found when trying to get lock; try restarting transaction exception is thrown immediately while executing saveOne method parallel to saveMany method. Deadlock found when trying to get lock; try restarting transaction异常而执行被立即抛出saveOne方法并行saveMany方法。

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

Since there are no indexes on the relevant columns, all rows are locked.由于相关列上没有索引,所有行都被锁定。

It would be better to have最好有

INDEX(br_id)

or或者

INDEX(br_id, val)

The latter is better for locating the row(s) to modify, but has the drawback of needing to update the index's BTree.后者更适合定位要修改的行,但缺点是需要更新索引的 BTree。

Be aware that some "locks" are "gap locks".请注意,某些“锁”是“间隙锁”。 This involves locking the gap between indexed values.这涉及锁定索引值之间的差距。 This can sometimes cause surprises.这有时会引起意外。 (I don't know if such is relevant to this update.) (我不知道这是否与此更新有关。)

One tool for looking at the locks is SHOW ENGINE INNODB STATUS;查看锁的一种工具是SHOW ENGINE INNODB STATUS; (Since the info there is transient, it may not show what you need.) (由于那里的信息是暂时的,它可能无法显示您需要的信息。)

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

That will either delay or deadlock any competing connection that needs the same row locks.这将延迟或死锁需要相同行锁的任何竞争连接。

Nearly always InnoDB can see that simply stalling (up to innodb_lock_wait_timeout seconds) in order to get the necessary locks.几乎总是 InnoDB 可以看到简单地停止(最多innodb_lock_wait_timeout秒)以获得必要的锁。

InnoDB can always discover a deadlock, and will ROLLBACK one of the transactions, while letting the other finish. InnoDB总是可以发现死锁,并且会ROLLBACK其中一个事务,同时让另一个完成。

There are a few cases (esp with "gap locking" where InnoDB conservatively declares a deadlock when there might not have been one. (I don't know if this was your case.)有几种情况(尤其是“间隙锁定”,其中 InnoDB 在可能没有死锁的情况下保守地声明一个死锁。(我不知道这是否是您的情况。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM