[英]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 ![]() |
Bigint![]() |
PK AI ![]() |
name![]() |
Varchar![]() |
|
val![]() |
Int![]() |
|
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
}
update
query will not update anything.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:从上面的案例这里是我的推导:
inserts
or updates
does not lock tablesinserts
或updates
不会锁定表insert
and update
will lock the table (whichever operation is triggered first will acquire lock first)insert
和update
会锁表(先触发哪个操作先获取锁) 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 中锁定,并且只有在执行
inserts
或updates
时才锁定行。 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?此外,并行
inserts
或updates
不会导致任何问题,如何以及为什么?
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.