Skip to content

Commit b9b9b37

Browse files
Aditya Altangvald
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. #rb 22659 22909 Reviewed by : Debarun Banerjee <[email protected]> (cherry picked from commit 0b49d345b65faade7d20a0cf4c7f2104abe61107)
1 parent 12bc608 commit b9b9b37

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: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,64 @@ 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+
SELECT ZZtest() AS test;
93+
test
94+
0
95+
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
@@ -10,7 +10,6 @@ CREATE TABLE t1(f1 int primary key,
1010
f2 int, f3 int, unique key(f2))engine=innodb;
1111

1212
SHOW CREATE TABLE t1;
13-
1413
INSERT INTO t1(f1, f2, f3) VALUES(1, 10, 100);
1514

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

storage/innobase/row/row0uins.cc

Lines changed: 14 additions & 5 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
66
it under the terms of the GNU General Public License, version 2.0,
@@ -208,6 +208,7 @@ row_undo_ins_remove_sec_low(
208208
mtr_t mtr;
209209
enum row_search_result search_result;
210210
ibool modify_leaf = false;
211+
ulint rec_deleted;
211212

212213
log_free_check();
213214

@@ -253,18 +254,26 @@ row_undo_ins_remove_sec_low(
253254
ut_error;
254255
}
255256

257+
rec_deleted = rec_get_deleted_flag(btr_pcur_get_rec(&pcur),
258+
dict_table_is_comp(index->table));
259+
256260
if (search_result == ROW_FOUND && dict_index_is_spatial(index)) {
257-
rec_t* rec = btr_pcur_get_rec(&pcur);
258-
if (rec_get_deleted_flag(rec,
259-
dict_table_is_comp(index->table))) {
261+
if(rec_deleted) {
260262
ib::error() << "Record found in index " << index->name
261263
<< " is deleted marked on insert rollback.";
262264
}
263265
}
264266

265267
btr_cur = btr_pcur_get_btr_cur(&pcur);
266268

267-
row_convert_impl_to_expl_if_needed(btr_cur, node);
269+
if (rec_deleted == 0) {
270+
/* This record is not delete marked and has an implicit
271+
lock on it. For delete marked record, INSERT has not
272+
modified it yet and we don't have implicit lock on it.
273+
We must convert to explicit if and only if we have
274+
implicit lock on the record.*/
275+
row_convert_impl_to_expl_if_needed(btr_cur, node);
276+
}
268277

269278
if (modify_leaf) {
270279
err = btr_cur_optimistic_delete(btr_cur, 0, &mtr)

storage/innobase/row/row0umod.cc

Lines changed: 21 additions & 6 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
66
it under the terms of the GNU General Public License, version 2.0,
@@ -132,6 +132,10 @@ row_undo_mod_clust_low(
132132
*rebuilt_old_pk = NULL;
133133
}
134134

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

@@ -226,7 +230,6 @@ row_undo_mod_remove_clust_low(
226230
than the rolling-back one. */
227231
ut_ad(rec_get_deleted_flag(btr_cur_get_rec(btr_cur),
228232
dict_table_is_comp(node->table)));
229-
row_convert_impl_to_expl_if_needed(btr_cur, node);
230233

231234
if (mode == BTR_MODIFY_LEAF) {
232235
err = btr_cur_optimistic_delete(btr_cur, 0, mtr)
@@ -360,6 +363,8 @@ row_undo_mod_clust(
360363

361364
btr_pcur_commit_specify_mtr(pcur, &mtr);
362365

366+
DEBUG_SYNC_C("ib_undo_mod_before_remove_clust");
367+
363368
if (err == DB_SUCCESS && node->rec_type == TRX_UNDO_UPD_DEL_REC) {
364369

365370
mtr_start(&mtr);
@@ -424,6 +429,7 @@ row_undo_mod_del_mark_or_remove_sec_low(
424429
mtr_t mtr_vers;
425430
row_search_result search_result;
426431
ibool modify_leaf = false;
432+
ulint rec_deleted;
427433

428434
log_free_check();
429435
mtr_start(&mtr);
@@ -505,23 +511,32 @@ row_undo_mod_del_mark_or_remove_sec_low(
505511
btr_pcur_get_rec(&(node->pcur)),
506512
&mtr_vers, index, entry,
507513
0, 0);
514+
515+
/* If the key is delete marked then the statement could not modify the
516+
key yet and the transaction has no implicit lock on it. We must convert
517+
to explicit lock if and only if we are the transaction which has implicit
518+
lock on it.Note that it is still ok to purge the delete mark key if it is
519+
purgeable.*/
520+
rec_deleted = rec_get_deleted_flag(btr_pcur_get_rec(&pcur),
521+
dict_table_is_comp(index->table));
522+
if (rec_deleted == 0) {
523+
row_convert_impl_to_expl_if_needed(btr_cur, node);
524+
}
525+
508526
if (old_has) {
509527
err = btr_cur_del_mark_set_sec_rec(BTR_NO_LOCKING_FLAG,
510528
btr_cur, TRUE, thr, &mtr);
511529
ut_ad(err == DB_SUCCESS);
512530
} else {
513531
/* Remove the index record */
514532
if (dict_index_is_spatial(index)) {
515-
rec_t* rec = btr_pcur_get_rec(&pcur);
516-
if (rec_get_deleted_flag(rec,
517-
dict_table_is_comp(index->table))) {
533+
if (rec_deleted) {
518534
ib::error() << "Record found in index "
519535
<< index->name << " is deleted marked"
520536
" on rollback update.";
521537
}
522538
}
523539

524-
row_convert_impl_to_expl_if_needed(btr_cur, node);
525540

526541
if (modify_leaf) {
527542
success = btr_cur_optimistic_delete(btr_cur, 0, &mtr);

storage/innobase/row/row0undo.cc

Lines changed: 2 additions & 1 deletion
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
66
it under the terms of the GNU General Public License, version 2.0,
@@ -387,6 +387,7 @@ row_convert_impl_to_expl_if_needed(
387387

388388
if (heap_no != PAGE_HEAP_NO_SUPREMUM
389389
&& !dict_table_is_intrinsic(index->table)
390+
&& !dict_table_is_temporary(index->table)
390391
&& !dict_index_is_spatial(index)) {
391392
lock_rec_convert_active_impl_to_expl(block, rec, index,
392393
offsets,node->trx,heap_no);

0 commit comments

Comments
 (0)