[MDEV-18706] ER_LOCK_DEADLOCK on concurrent read and insert into already locked gap Created: 2019-02-23 Updated: 2024-01-16 |
|
| Status: | Stalled |
| Project: | MariaDB Server |
| Component/s: | Storage Engine - InnoDB, Storage Engine - XtraDB |
| Affects Version/s: | 5.5, 10.1, 10.2, 10.3, 10.4 |
| Fix Version/s: | 10.4 |
| Type: | Bug | Priority: | Major |
| Reporter: | Elena Stepanova | Assignee: | Vladislav Lesin |
| Resolution: | Unresolved | Votes: | 1 |
| Labels: | lock, performance | ||
| Attachments: |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Issue Links: |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Description |
| Comments |
| Comment by Aleksey Midenkov [ 2019-08-06 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Reproducible without System versioning:
CauseNo real deadlock, but incomplete conflict detection: 1. T1-S-W waits on T2-X lock; The weak point here is 2. (lock_rec_has_to_wait()), because T2-II-W doesn't have to wait T1: T2 already got X next-key lock that also holds the gap before the record. FixAllow lock in lock_rec_other_has_conflicting() if there is already non-waiting stronger or equal lock in this transaction (lock_rec_has_expl()). | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2019-09-09 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I can repeat the deadlock on the latest 5.5, 10.1, 10.2 as well. The bug could exist since MySQL 4.0 or even earlier. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2019-09-09 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Sorry for the delay. I agree that the deadlock looks bogus, but I would like the fix to be a little cleaner, to reduce the potential of correctness regressions around locking reads. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2019-10-18 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I made some similar observations when debugging a test case for | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2020-05-12 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
By info provided in | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-08-12 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Can you please rebase the fix to a recent main branch? I’d review it after a successful CI run. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2020-08-25 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
bb-10.2-midenok | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-11-05 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I updated bb-10.2-midenok-innodb today with the latest 10.2. The old results were no longer available in the grid view, but in the cross-reference I can see that the test galera.MW-328D was failing also for earlier versions of the branch. That test last failed in any other 10.2-based branch in 2018. So, it looks like this (or | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Matthias Leich [ 2020-11-05 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2021-06-17 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Rebased bb-10.3-midenok-MDEV-18706 with the latest 10.3 revision. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2021-06-17 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
midenok, I think that it would be much more useful to test this in a 10.6-based branch, where the locking subsystem was improved in | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2021-07-05 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Updated bb-10.6-midenok-MDEV-18706 Please review and do additional testing. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Matthias Leich [ 2021-07-08 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2021-07-12 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
The failure seems to be caused by
Latch is released at 365, then another thread changes the record and assertion fails at 385. See failing_thread.txt and tampering_thread.txt for stack traces. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2021-07-20 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
mleich says that the simplified RQG grammar does not fail for him on the latest 10.6 (without the code changes) but fails very easily with the code changes. Locking forms up to 3 cornerstones of ACID. Records in transactions are protected in 2 means:
ROLLBACK operations are always supposed to be covered by an exclusive record lock of either type. The correctness of rollback must not rely on page latches. A failure in the highlighted code suggests that the being-rolled-back transaction is not holding a lock on the record that is being modified. This strongly suggests that there is an error in the suggested code change. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2021-07-20 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I quickly read the patch. I noticed an uncovered DEBUG_SYNC_C("row_search_lock_wait"). Also, the purpose and the correctness of tweaking the restored persistent cursor position in row_search_mvcc() is not clear to me. We must also keep in mind that in 10.3, Given that this change deals with gap locks, it should be better to address | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2021-07-20 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
In which way would this fix differ from the following MySQL change? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2021-07-21 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
This is similar but more complex approach to solve | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2021-07-21 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
I suspect the situation is more complex: race condition between undo, purge and optimistic insert. While row_undo_mod_clust() does mtr commit, purge thread frees the record and sets PAGE_FREE field to point to that record (heap_no 23). Then 3rd thread gets into the scene and inserts into that record. That 3rd thread first tries row_ins_clust_index_entry_by_modify() but fails to lock heap_no 23: undo thread holds S-lock (maybe X-lock as well, but I've seen S-lock). After that 3rd thread does btr_cur_optimistic_insert() and checks the gap at heap_no 2. The gap is free to insert (LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION). Optimistic insert tries to insert after infimum: that's why it locks heap_no 2 instead of 23. But when it does page_cur_insert_rec_low() and finds free record via PAGE_FREE field it gets record 23. Meanwhile undo thread is still at that mtr commit call. In short words: insert thread tried to insert by update but failed to lock as expected, then it tried optimistic insert and succeeded to write into the very same record where it failed in the first attempt. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2021-07-26 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Reproduced by v.test. New lock X|GAP|INSERT_INTENTION was considered weaker than X|GAP. That Fixed by denying pure GAP to be stronger than GAP|INSERT_INTENTION. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2021-07-26 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
mleich Can you please test again bb-10.6-midenok-MDEV-18706 ? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2021-09-29 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
I think that this is best reviewed (and initially tested) by vlad.lesin, after he is done with the gap lock fixes that were observed in | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Vladislav Lesin [ 2023-08-08 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Why the case is considered as a bug? 1. trx 1 executes "update t1 set x= 1;" and X-locks (3,1) record It behaves as expected. If trx 1 did not conflict with waiting trx 2 lock on step 3, it would be ACID violation, as trx 2 is executed in "repeatable read" isolation level. See also this comment and | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Vladislav Lesin [ 2023-08-08 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
It looks like | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2023-08-11 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
vlad.lesin Transactions in 2. and 3. are coming in parallel. Nobody defined 2. comes before 3. except the system itself. System made wrong choice since transaction 2. is cancelled. Think of it as transaction 3. comes before 2. I remember I checked this, but I rechecked:
AFAICS no inconsistency on rollback. Probably this should be added to test too. vlad.lesin maybe I'm missing something? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Vladislav Lesin [ 2023-08-15 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
> Think of it as transaction 3. comes before 2. There are only 2 active transactions in the scenario. I suppose you meant steps 2 and 3, not transactions. If so, ok suppose trx 1 is requesting ii-lock on (2,1) record on step 3, executing "insert into t1 values (2, 1);", before trx 2 locked it on step 2, executing "select * from t1 for update". Then trx 1 should acquire the lock, and trx 2 should be blocked until trx 1 is finished. But you insist that trx 2 is killed by deadlock resolver. If so, do you have any test which would confirm it? The test does not suite, see wait condition there. Please correct me if I did not understand your argument. > System made wrong choice since transaction 2. is cancelled. What exactly subsystem do you mean? Deadlock resolver counts transaction weights. The more locks and undo log records transaction holds, the more its weight. Transaction with less weight is being chosen as deadlock resolver victim. So the weight of trx 1 is greater then the one of trx2, that's why trx2 is cancelled. Or you mean something else? By ACID violation I meant the following. trx 2 is executed on RR isolation level. Trx 2 locked some range before trx 1 is inserted something in the range(see wait condition in the test), what means trx 2 must not see any records produced by trx 1 in that range. As the read is locking, MVCC is not used. If trx 1 inserts something in the locked by trx 2 gap, and trx 2 re-reads the containing the gap range, it will get record which must not be seen for trx 2. That's why trx 1 ii-lock conflicts with waiting trx2 ordinary(record + preceding gap) lock. I.e. when persistent cursor position is restored after trx 2 is awakened (see sel_restore_position_for_mysql() call after row_mysql_handle_errors() in row_search_mvcc()), it will be set on (3,1) record, and "select * from t1 for update;" will miss (2,1) record. If trx 2 is not auto-committed, and then executes the same "select * from t1 for update;" once more, it will see (2,1) record, what is isolation level violation. And cancelling trx2 with deadlock error is less evil then isolation violation. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2023-08-15 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Yes, transaction 1. and 3. is the same transaction trx 2. I didn't write "from step" for brevity. vlad.lesin I don't insist anything, please see task description. You can test it yourself, it does cancel SELECT. > What exactly subsystem do you mean? I don't mean exact subsystem. I mean user POV. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Aleksey Midenkov [ 2023-08-15 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
vlad.lesin Can you show the test case with ACID violation, please? | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Vladislav Lesin [ 2023-08-15 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
midenok, ok, here is modified test with different locking order:
And there is no deadlock error, as expected. As well as there is deadlock error in the original test. What is also expected(I explained it in details in the my previous comment). | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Comment by Vladislav Lesin [ 2023-08-15 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
midenok, |