Skip to content

Commit 7ade669

Browse files
Aditya Adahlerlend
authored andcommitted
Bug #29195848 ASSERTION "!OTHER_LOCK" IN LOCK_REC_ADD_TO_QUEUE
PROBLEM ------- The root cause of the problem was that delete marked rows can acquire a external read lock at the stage where partial rollback is not complete In partial rollback when we try to convert implicit lock to explicit we get a assert saying that already it is locked by a external read lock. 1. For Secondary Index: During rollback, we can remove delete marked key (that is ok to purge) even if the transaction hasn't modified it. In such case it is not right to convert to explicit lock since the transaction is not holding implicit lock on the key. 2. For Cluster Index: If INSERT has modified an existing delete marked key, then during rollback there are 2 steps. A. Rollback the update replacing with previous key (delete mark) B. Remove the delete mark on the row if it is ok to purged The implicit lock is released at step A. Currently we were creating the explicit lock in step-B which keeps a window when the key is not locked allowing other transaction to lock the row. We must convert implicit to explicit lock before step-A. FIX --- We are fixing the problem with these three steps 1) When reverting back the change done in cluster record we were leaving the protection of the implicit lock when we did a commit in the function row_undo_mod_clust() which enabled other connection to acquire a lock on the row. So we try to do a implicit to explicit conversion before the commit. 2) For secondary index records we don't allow the implicit to explicit conversion if the record is delete marked. 3) Regression caused by IODKU patch (# Bug #29718243 MYQL SERVER CRASHING) is fixed by not allowing temporary tables to do implicit to explicit conversions since temporary tables are per session. Reviewed by : Debarun Banerjee <[email protected]>
1 parent 9a14d83 commit 7ade669

File tree

5 files changed

+256
-13
lines changed

5 files changed

+256
-13
lines changed

mysql-test/suite/innodb/r/iodku_debug.result

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,66 @@ f1 f2 f3
3232
2 20 120
3333
SET DEBUG_SYNC ='RESET';
3434
DROP TABLE t1;
35+
#
36+
# Bug #29195848 ASSERTION "!OTHER_LOCK" IN LOCK_REC_ADD_TO_QUEUE
37+
#
38+
SET GLOBAL innodb_purge_stop_now = ON;
39+
CREATE TABLE t1 (id INT NOT NULL PRIMARY KEY, b INT, UNIQUE KEY(b));
40+
INSERT INTO t1 VALUES(2, 300);
41+
DELETE FROM t1;
42+
INSERT INTO t1 VALUES(3, 300);
43+
SELECT * FROM t1;
44+
id b
45+
3 300
46+
SET DEBUG_SYNC='ib_after_row_insert_step SIGNAL after_insert WAIT_FOR
47+
rollback';
48+
INSERT INTO t1 VALUES(2, 300);;
49+
SET DEBUG_SYNC='now WAIT_FOR after_insert';
50+
START TRANSACTION;
51+
INSERT INTO t1 VALUES(1, 300);
52+
ERROR 23000: Duplicate entry '300' for key 'b'
53+
SET GLOBAL innodb_purge_run_now=ON;
54+
SET DEBUG_SYNC='now SIGNAL rollback';
55+
ERROR 23000: Duplicate entry '300' for key 'b'
56+
COMMIT;
57+
DROP TABLE t1;
58+
SET GLOBAL innodb_purge_stop_now = ON;
59+
CREATE TABLE t1 (a INT PRIMARY KEY, b INT, UNIQUE KEY(b));
60+
INSERT INTO t1 VALUES(1,10);
61+
DELETE FROM t1;
62+
INSERT INTO t1 VALUES(2,10);
63+
SET DEBUG_SYNC='ib_undo_mod_before_remove_clust SIGNAL during_rollback
64+
WAIT_FOR rollback';
65+
INSERT INTO t1 VALUES(1, 10) ;;
66+
SET DEBUG_SYNC='now WAIT_FOR during_rollback';
67+
SELECT * FROM t1 WHERE a = 1 LOCK IN SHARE MODE;
68+
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
69+
SET GLOBAL innodb_purge_run_now=ON;
70+
SET DEBUG_SYNC='now SIGNAL rollback';
71+
ERROR 23000: Duplicate entry '10' for key 'b'
72+
SELECT * FROM t1;
73+
a b
74+
2 10
75+
DROP TABLE t1;
76+
#
77+
# Bug #29718243 MYQL SERVER CRASHING
78+
#
79+
CREATE TEMPORARY TABLE tmpTest(tmpField INT , UNIQUE KEY uq_tmpField (tmpField));
80+
CREATE TEMPORARY TABLE tmpTest1(tmpField INT , UNIQUE KEY uq_tmpField (tmpField));
81+
CREATE FUNCTION ZZtest() RETURNS int(11)
82+
BEGIN
83+
DECLARE l_total INTEGER;
84+
SET l_total = 0;
85+
INSERT INTO tmpTest SET tmpField = 40;
86+
INSERT IGNORE INTO tmpTest SET tmpField = 40;
87+
INSERT IGNORE INTO tmpTest1 SET tmpField = 40;
88+
DROP TEMPORARY TABLE IF EXISTS tmpTest;
89+
DROP TEMPORARY TABLE IF EXISTS tmpTest1;
90+
RETURN l_total;
91+
END|
92+
Warnings:
93+
Warning 1681 Integer display width is deprecated and will be removed in a future release.
94+
SELECT ZZtest() AS test;
95+
test
96+
0
97+
DROP FUNCTION ZZtest;

mysql-test/suite/innodb/t/iodku_debug.test

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ CREATE TABLE t1(f1 int primary key,
99
f2 int, f3 int, unique key(f2))engine=innodb;
1010

1111
SHOW CREATE TABLE t1;
12-
1312
INSERT INTO t1(f1, f2, f3) VALUES(1, 10, 100);
1413

1514
--echo # Connection default
@@ -41,3 +40,161 @@ SELECT * FROM t1;
4140
SET DEBUG_SYNC ='RESET';
4241
DROP TABLE t1;
4342
--source include/wait_until_count_sessions.inc
43+
44+
--echo #
45+
--echo # Bug #29195848 ASSERTION "!OTHER_LOCK" IN LOCK_REC_ADD_TO_QUEUE
46+
--echo #
47+
# Test 1 :- Partial rollback of Insert (secondary Index):
48+
# Pre condition-1: Secondary index has one unpurged delete marked key
49+
# [say K1] that matches exactly with the key to be inserted
50+
# (including cluster index key).
51+
# Pre-condition-2: Secondary index has one committed key [say K2] that matches
52+
# with the key to be inserted but with different cluster index key.
53+
54+
# Test: During Insert, secondary index entry [K1] is not updated (un-delete marked)
55+
# because of duplicate key [K2].In this case the transaction doesn't
56+
# hold implicit lock on secondary index key and partial rollback should
57+
# not try to convert it to explicit lock.
58+
59+
SET GLOBAL innodb_purge_stop_now = ON;
60+
CREATE TABLE t1 (id INT NOT NULL PRIMARY KEY, b INT, UNIQUE KEY(b));
61+
62+
INSERT INTO t1 VALUES(2, 300);
63+
#It generates the delete marked key [K1]
64+
DELETE FROM t1;
65+
66+
#It generates the committed key [K2] 300,3 in Secondary index.
67+
INSERT INTO t1 VALUES(3, 300);
68+
SELECT * FROM t1;
69+
70+
connect (conn1,localhost,root,,);
71+
connect (conn2,localhost,root,,);
72+
73+
connection conn1;
74+
SET DEBUG_SYNC='ib_after_row_insert_step SIGNAL after_insert WAIT_FOR
75+
rollback';
76+
77+
# We have inserted successfully the cluster index key [2]
78+
# when ib_after_row_insert_step is reached. We failed to un-delete mark
79+
# the secondary index key K1 [300,2] because of duplicate key K2 [300,3].
80+
# So key K1 [300,2] stays delete marked and the transaction have shared
81+
# lock on it instead of implicit lock.
82+
--send INSERT INTO t1 VALUES(2, 300);
83+
84+
connection conn2;
85+
SET DEBUG_SYNC='now WAIT_FOR after_insert';
86+
START TRANSACTION;
87+
# We acquire S lock on delete marked secondary index key K1 [300, 2].
88+
--error ER_DUP_ENTRY
89+
INSERT INTO t1 VALUES(1, 300);
90+
91+
connection default;
92+
# For purge going
93+
SET GLOBAL innodb_purge_run_now=ON;
94+
SET DEBUG_SYNC='now SIGNAL rollback';
95+
96+
connection conn1;
97+
# The transaction is partially rolled back here.
98+
# Before the fix, it would assert while trying to create explicit lock on K1 [300,2]
99+
# because other transaction already holds shared lock on it.
100+
# Since we don't have implicit lock here, the fix makes sure that we don't try to
101+
# create explicit lock during such partial rollback for a delete marked record.
102+
--error ER_DUP_ENTRY
103+
--reap
104+
105+
connection conn2;
106+
COMMIT;
107+
108+
disconnect conn1;
109+
disconnect conn2;
110+
connection default;
111+
DROP TABLE t1;
112+
113+
# Test 2 Partial Rollback of Insert (Cluster Index):
114+
# Pre condition-1: Cluster index has one unpurged delete marked key [say PK1]
115+
# that matches exactly with the key to be inserted.
116+
# Pre-condition-2: Unique 2ndary index has one committed key [say K1] that matches
117+
# with the key to be inserted but with different cluster index key.
118+
# Test: During Insert, the cluster index Key K1 is reused by undelete marking it.
119+
# The command fails and goes for partial rollback because of matching secondary
120+
# index key K1. During partial rollback, the implicit lock is released when we
121+
# update the cluster index key replacing with previous delete marked key.The
122+
# test verifies that the implicit lock is converted to explicit lock before
123+
# releasing it.
124+
125+
SET GLOBAL innodb_purge_stop_now = ON;
126+
CREATE TABLE t1 (a INT PRIMARY KEY, b INT, UNIQUE KEY(b));
127+
128+
#Create delete marked key PK1 (1) in cluster index and (10, 1) K1 in Secondary index.
129+
INSERT INTO t1 VALUES(1,10);
130+
DELETE FROM t1;
131+
132+
#Create committed key (2) in cluster index and (10, 2) K2 in Secondary index.
133+
INSERT INTO t1 VALUES(2,10);
134+
135+
connect (conn1,localhost,root,,);
136+
connect (conn2,localhost,root,,);
137+
138+
connection conn1;
139+
SET DEBUG_SYNC='ib_undo_mod_before_remove_clust SIGNAL during_rollback
140+
WAIT_FOR rollback';
141+
142+
#Try to Insert with cluster index key matching PK1
143+
--send INSERT INTO t1 VALUES(1, 10) ;
144+
145+
connection conn2;
146+
147+
SET DEBUG_SYNC='now WAIT_FOR during_rollback';
148+
# In Conn1, we have got duplicate key error due to mathing secondary index key
149+
# and at this stage half way through partial rollback. We are waiting after rolling
150+
# back cluster index key with old delete marked key K1. The implicit lock on PK1
151+
# is already released.Try to lock the cluster index key PK1. The bug has the issue
152+
# that Conn1 transaction has not created the explicit lock on PK1 but implicit lock
153+
# is already released,so taking a shared lock would succeeded which caused assert
154+
# when conn1 tried to rollback. After the fix we ensure that implicit lock is
155+
# converted to explicit before the implicit protection is released,therfore the
156+
# share lock request times out
157+
158+
--error ER_LOCK_WAIT_TIMEOUT
159+
SELECT * FROM t1 WHERE a = 1 LOCK IN SHARE MODE;
160+
161+
connection default;
162+
SET GLOBAL innodb_purge_run_now=ON;
163+
SET DEBUG_SYNC='now SIGNAL rollback';
164+
165+
connection conn1;
166+
--error ER_DUP_ENTRY
167+
--reap
168+
169+
disconnect conn1;
170+
disconnect conn2;
171+
connection default;
172+
SELECT * FROM t1;
173+
DROP TABLE t1;
174+
175+
176+
--echo #
177+
--echo # Bug #29718243 MYQL SERVER CRASHING
178+
--echo #
179+
180+
CREATE TEMPORARY TABLE tmpTest(tmpField INT , UNIQUE KEY uq_tmpField (tmpField));
181+
CREATE TEMPORARY TABLE tmpTest1(tmpField INT , UNIQUE KEY uq_tmpField (tmpField));
182+
183+
DELIMITER |;
184+
CREATE FUNCTION ZZtest() RETURNS int(11)
185+
BEGIN
186+
DECLARE l_total INTEGER;
187+
SET l_total = 0;
188+
189+
INSERT INTO tmpTest SET tmpField = 40;
190+
INSERT IGNORE INTO tmpTest SET tmpField = 40;
191+
INSERT IGNORE INTO tmpTest1 SET tmpField = 40;
192+
193+
DROP TEMPORARY TABLE IF EXISTS tmpTest;
194+
DROP TEMPORARY TABLE IF EXISTS tmpTest1;
195+
196+
RETURN l_total;
197+
END|
198+
DELIMITER ;|
199+
SELECT ZZtest() AS test;
200+
DROP FUNCTION ZZtest;

storage/innobase/row/row0uins.cc

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t
117117
}
118118

119119
row_convert_impl_to_expl_if_needed(btr_cur, node);
120-
121120
if (btr_cur_optimistic_delete(btr_cur, 0, &mtr)) {
122121
err = DB_SUCCESS;
123122
goto func_exit;
@@ -176,6 +175,7 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t
176175
mtr_t mtr;
177176
enum row_search_result search_result;
178177
ibool modify_leaf = false;
178+
ulint rec_deleted;
179179

180180
log_free_check();
181181

@@ -220,17 +220,26 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t
220220
ut_error;
221221
}
222222

223+
rec_deleted = rec_get_deleted_flag(btr_pcur_get_rec(&pcur),
224+
dict_table_is_comp(index->table));
225+
223226
if (search_result == ROW_FOUND && dict_index_is_spatial(index)) {
224-
rec_t *rec = btr_pcur_get_rec(&pcur);
225-
if (rec_get_deleted_flag(rec, dict_table_is_comp(index->table))) {
227+
if (rec_deleted) {
226228
ib::error(ER_IB_MSG_1036) << "Record found in index " << index->name
227229
<< " is deleted marked on insert rollback.";
228230
}
229231
}
230232

231233
btr_cur = btr_pcur_get_btr_cur(&pcur);
232234

233-
row_convert_impl_to_expl_if_needed(btr_cur, node);
235+
if (rec_deleted == 0) {
236+
/* This record is not delete marked and has an implicit
237+
lock on it. For delete marked record, INSERT has not
238+
modified it yet and we don't have implicit lock on it.
239+
We must convert to explicit if and only if we have
240+
implicit lock on the record.*/
241+
row_convert_impl_to_expl_if_needed(btr_cur, node);
242+
}
234243

235244
if (modify_leaf) {
236245
err = btr_cur_optimistic_delete(btr_cur, 0, &mtr) ? DB_SUCCESS : DB_FAIL;

storage/innobase/row/row0umod.cc

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t row_undo_mod_clust_low(
131131
*rebuilt_old_pk = NULL;
132132
}
133133

134+
/* Update would release the implicit lock. Must convert to
135+
explicit lock before applying update undo.*/
136+
row_convert_impl_to_expl_if_needed(btr_cur, node);
137+
134138
if (mode != BTR_MODIFY_TREE) {
135139
ut_ad((mode & ~BTR_ALREADY_S_LATCHED) == BTR_MODIFY_LEAF);
136140

@@ -214,8 +218,6 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t row_undo_mod_remove_clust_low(
214218
ut_ad(rec_get_deleted_flag(btr_cur_get_rec(btr_cur),
215219
dict_table_is_comp(node->table)));
216220

217-
row_convert_impl_to_expl_if_needed(btr_cur, node);
218-
219221
if (mode == BTR_MODIFY_LEAF) {
220222
err = btr_cur_optimistic_delete(btr_cur, 0, mtr) ? DB_SUCCESS : DB_FAIL;
221223
} else {
@@ -329,6 +331,8 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t
329331

330332
btr_pcur_commit_specify_mtr(pcur, &mtr);
331333

334+
DEBUG_SYNC_C("ib_undo_mod_before_remove_clust");
335+
332336
if (err == DB_SUCCESS && node->rec_type == TRX_UNDO_UPD_DEL_REC) {
333337
mtr_start(&mtr);
334338

@@ -386,6 +390,7 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t
386390
mtr_t mtr_vers;
387391
row_search_result search_result;
388392
ibool modify_leaf = false;
393+
ulint rec_deleted;
389394

390395
log_free_check();
391396

@@ -463,6 +468,17 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t
463468
btr_pcur_restore_position(BTR_SEARCH_LEAF, &(node->pcur), &mtr_vers);
464469
ut_a(success);
465470

471+
/* If the key is delete marked then the statement could not modify the
472+
key yet and the transaction has no implicit lock on it. We must convert
473+
to explicit lock if and only if we are the transaction which has implicit
474+
lock on it.Note that it is still ok to purge the delete mark key if it
475+
is purgeable.*/
476+
rec_deleted = rec_get_deleted_flag(btr_pcur_get_rec(&pcur),
477+
dict_table_is_comp(index->table));
478+
if (rec_deleted == 0) {
479+
row_convert_impl_to_expl_if_needed(btr_cur, node);
480+
}
481+
466482
old_has = row_vers_old_has_index_entry(FALSE, btr_pcur_get_rec(&(node->pcur)),
467483
&mtr_vers, index, entry, 0, 0);
468484
if (old_has) {
@@ -473,16 +489,13 @@ static MY_ATTRIBUTE((warn_unused_result)) dberr_t
473489
/* Remove the index record */
474490

475491
if (dict_index_is_spatial(index)) {
476-
rec_t *rec = btr_pcur_get_rec(&pcur);
477-
if (rec_get_deleted_flag(rec, dict_table_is_comp(index->table))) {
492+
if (rec_deleted) {
478493
ib::error(ER_IB_MSG_1038) << "Record found in index " << index->name
479494
<< " is deleted marked"
480495
" on rollback update.";
481496
}
482497
}
483498

484-
row_convert_impl_to_expl_if_needed(btr_cur, node);
485-
486499
if (modify_leaf) {
487500
success = btr_cur_optimistic_delete(btr_cur, 0, &mtr);
488501
if (success) {

storage/innobase/row/row0undo.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*****************************************************************************
22
3-
Copyright (c) 1997, 2018, Oracle and/or its affiliates. All Rights Reserved.
3+
Copyright (c) 1997, 2019, Oracle and/or its affiliates. All Rights Reserved.
44
55
This program is free software; you can redistribute it and/or modify it under
66
the terms of the GNU General Public License, version 2.0, as published by the
@@ -333,7 +333,8 @@ void row_convert_impl_to_expl_if_needed(btr_cur_t *cursor, undo_node_t *node) {
333333
auto block = btr_cur_get_block(cursor);
334334
auto heap_no = page_rec_get_heap_no(rec);
335335

336-
if (heap_no != PAGE_HEAP_NO_SUPREMUM && !dict_index_is_spatial(index)) {
336+
if (heap_no != PAGE_HEAP_NO_SUPREMUM && !dict_index_is_spatial(index) &&
337+
!index->table->is_temporary() && !index->table->is_intrinsic()) {
337338
lock_rec_convert_active_impl_to_expl(block, rec, index, offsets, node->trx,
338339
heap_no);
339340
}

0 commit comments

Comments
 (0)