Skip to content

Commit 1badccd

Browse files
Jakub ŁopuszańskiRahulSisondia
authored andcommitted
Bug#36846567 Inplace ALTER TABLE might cause lost rows if concurrent purge [Post-push fix]
Bug#37318367 Inplace ALTER on table with spatial idx might cause lost rows if concurrent purge [Post-push fix] Description: ------------ This is post push fix of the above Bugs. There is another method PCursor::move_to_next_block() where the scan is paused and latches are released. This method was missed to be addressed, as a result ALTER TABLE rebuild might lose records if latches are released in this case. Background: ----------- When doing a parallel scan, Parallel_reader::Ctx::traverse() calls Parallel_reader::Ctx::traverse_recs() which does: ``` /* Note: The page end callback (above) can save and restore the cursor. The restore can end up in the middle of a page. */ if (pcursor->is_after_last_on_page() && !move_to_next_node(pcursor)) { break; } ``` Where Parallel_reader::Ctx::move_to_next_node() calls PCursor::move_to_next_block which uses PCursor::savepoint() and PCursor::restore_from_savepoint(). These functions are known to have problems which were addressed in the recent bug fixes, but they missed to fix this particular code path. The reason for that omission was that the PCursor::move_to_next_block() is also used during SELECT COUNT(*) at internal level (above leaves) for which the solution employed for ALTER will not work, because B-tree navigation functions do not support the required comparison mode when searching to non-leaf level. Fix: ---- This patch carves out the part of PCursor::move_to_next_block() which is handling the simple leaf-level case, for which it is well known what should be done: we are at supremum, all previous records were fully processed, and we should restore the cursor to a user record which is larger than any processed record. That can be accomplished by using the recently introduced methods save_previous_user_record_as_last_processed() and restore_to_first_unprocessed(), with a small adjustment, that here we require the cursor to be not only after all processed records, but also on a user record, so in case it is restored to supremum, we should additionally call move_to_user_rec(). This patch also makes it clear which methods are used, and where, by removing unused methods, marking some of them as private, and adding asserts that m_read_level!=0 to those related to SELECT COUNT() only. Change-Id: I2994374c542e168e0884e947634593ade17495ec
1 parent e7456a7 commit 1badccd

File tree

6 files changed

+268
-48
lines changed

6 files changed

+268
-48
lines changed

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

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
SET DEBUG_SYNC= 'RESET';
21
connect con1, localhost, root,,;
2+
CREATE PROCEDURE test.insert_records (IN record_count INT)
3+
BEGIN
4+
DECLARE counter INT DEFAULT 1;
5+
WHILE counter <= record_count DO
6+
INSERT INTO test.t1 VALUES (0);
7+
SET counter = counter + 1;
8+
END WHILE;
9+
END //
310
##############################################################
411
# Bug#36846567 Inplace ALTER TABLE might cause lost rows if concurrent purge
512
##############################################################
@@ -10,7 +17,7 @@ INSERT INTO t1 VALUES ('aaaaa'), ('bbbbb'), ('bbbcc'), ('ccccc'), ('ddddd'), ('e
1017
SET GLOBAL INNODB_PURGE_STOP_NOW=ON;
1118
DELETE FROM t1 WHERE pk = 'bbbcc';
1219
connection con1;
13-
SET DEBUG='+d,ddl_buf_add_two';
20+
SET SESSION DEBUG='+d,ddl_buf_add_two';
1421
SET DEBUG_SYNC='ddl_bulk_inserter_latches_released SIGNAL latches_released WAIT_FOR go';
1522
# Send ALTER TABLE INPLACE which rebuilds table.
1623
ALTER TABLE t1 ENGINE=InnoDB, ALGORITHM=INPLACE;
@@ -20,6 +27,7 @@ SET GLOBAL innodb_purge_run_now=ON;
2027
SET DEBUG_SYNC='now SIGNAL go';
2128
connection con1;
2229
# Reap ALTER TABLE
30+
SET SESSION DEBUG='-d,ddl_buf_add_two';
2331
# Before the fix row 'ddddd' was missing from the table after ALTER.
2432
SELECT * FROM t1;
2533
pk
@@ -28,10 +36,8 @@ bbbbb
2836
ccccc
2937
ddddd
3038
eeeee
31-
SET DEBUG_SYNC= 'RESET';
3239
# Test cleanup
3340
connection default;
34-
SET DEBUG_SYNC= 'RESET';
3541
DROP TABLE t1;
3642
##############################################################
3743
# Test#2 Delete the first record
@@ -43,7 +49,7 @@ INSERT INTO t1 VALUES ('aaaaa'), ('bbbbb'), ('bbbcc'), ('ccccc'), ('ddddd'), ('e
4349
SET GLOBAL INNODB_PURGE_STOP_NOW=ON;
4450
DELETE FROM t1 WHERE pk = 'aaaaa';
4551
connection con1;
46-
SET DEBUG='+d,ddl_buf_add_two';
52+
SET SESSION DEBUG='+d,ddl_buf_add_two';
4753
SET DEBUG_SYNC='ddl_bulk_inserter_latches_released SIGNAL latches_released WAIT_FOR go';
4854
# Send ALTER TABLE INPLACE which rebuilds table.
4955
ALTER TABLE t1 ENGINE=InnoDB, ALGORITHM=INPLACE;
@@ -53,6 +59,7 @@ SET GLOBAL innodb_purge_run_now=ON;
5359
SET DEBUG_SYNC='now SIGNAL go';
5460
connection con1;
5561
# Reap ALTER TABLE
62+
SET SESSION DEBUG='-d,ddl_buf_add_two';
5663
# Verify intended records are present
5764
SELECT * FROM t1;
5865
pk
@@ -61,10 +68,8 @@ bbbcc
6168
ccccc
6269
ddddd
6370
eeeee
64-
SET DEBUG_SYNC= 'RESET';
6571
# Test cleanup
6672
connection default;
67-
SET DEBUG_SYNC= 'RESET';
6873
DROP TABLE t1;
6974
##############################################################
7075
# Test#3 Delete the second record
@@ -76,7 +81,7 @@ INSERT INTO t1 VALUES ('aaaaa'), ('bbbbb'), ('bbbcc'), ('ccccc'), ('ddddd'), ('e
7681
SET GLOBAL INNODB_PURGE_STOP_NOW=ON;
7782
DELETE FROM t1 WHERE pk = 'bbbbb';
7883
connection con1;
79-
SET DEBUG='+d,ddl_buf_add_two';
84+
SET SESSION DEBUG='+d,ddl_buf_add_two';
8085
SET DEBUG_SYNC='ddl_bulk_inserter_latches_released SIGNAL latches_released WAIT_FOR go';
8186
# Send ALTER TABLE INPLACE which rebuilds table.
8287
ALTER TABLE t1 ENGINE=InnoDB, ALGORITHM=INPLACE;
@@ -86,6 +91,7 @@ SET GLOBAL innodb_purge_run_now=ON;
8691
SET DEBUG_SYNC='now SIGNAL go';
8792
connection con1;
8893
# Reap ALTER TABLE
94+
SET SESSION DEBUG='-d,ddl_buf_add_two';
8995
# Verify intended records are present
9096
SELECT * FROM t1;
9197
pk
@@ -94,10 +100,8 @@ bbbcc
94100
ccccc
95101
ddddd
96102
eeeee
97-
SET DEBUG_SYNC= 'RESET';
98103
# Test cleanup
99104
connection default;
100-
SET DEBUG_SYNC= 'RESET';
101105
DROP TABLE t1;
102106
##############################################################
103107
# Test#4 Delete the fourth record
@@ -109,7 +113,7 @@ INSERT INTO t1 VALUES ('aaaaa'), ('bbbbb'), ('bbbcc'), ('ccccc'), ('ddddd'), ('e
109113
SET GLOBAL INNODB_PURGE_STOP_NOW=ON;
110114
DELETE FROM t1 WHERE pk = 'ccccc';
111115
connection con1;
112-
SET DEBUG='+d,ddl_buf_add_two';
116+
SET SESSION DEBUG='+d,ddl_buf_add_two';
113117
SET DEBUG_SYNC='ddl_bulk_inserter_latches_released SIGNAL latches_released WAIT_FOR go';
114118
# Send ALTER TABLE INPLACE which rebuilds table.
115119
ALTER TABLE t1 ENGINE=InnoDB, ALGORITHM=INPLACE;
@@ -119,6 +123,7 @@ SET GLOBAL innodb_purge_run_now=ON;
119123
SET DEBUG_SYNC='now SIGNAL go';
120124
connection con1;
121125
# Reap ALTER TABLE
126+
SET SESSION DEBUG='-d,ddl_buf_add_two';
122127
# Verify intended records are present
123128
SELECT * FROM t1;
124129
pk
@@ -127,14 +132,43 @@ bbbbb
127132
bbbcc
128133
ddddd
129134
eeeee
130-
SET DEBUG_SYNC= 'RESET';
131135
# Test cleanup
132136
connection default;
133-
SET DEBUG_SYNC= 'RESET';
134137
DROP TABLE t1;
138+
############################################################
139+
# Test#5 Bug#37318367 Delete part of records from two pages
140+
# to lose a record - pause in PCursor::move_to_next_block
141+
############################################################
142+
SET GLOBAL innodb_limit_optimistic_insert_debug=3;
143+
connection default;
144+
CREATE TABLE t1 (id int AUTO_INCREMENT NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
145+
CALL insert_records(8);
146+
SET GLOBAL innodb_purge_stop_now = ON;
147+
DELETE FROM t1 WHERE id > 2 AND id < 7;
148+
connection con1;
149+
SET DEBUG_SYNC='pcursor_move_to_next_block_latches_released SIGNAL latches_released WAIT_FOR go EXECUTE 2';
150+
# Send ALTER TABLE INPLACE to rebuild the index.
151+
SET SESSION debug="+d,pcursor_move_to_next_block_release_latches";
152+
ALTER TABLE t1 ENGINE=InnoDB, ALGORITHM=INPLACE;
153+
connection default;
154+
SET DEBUG_SYNC='now WAIT_FOR latches_released';
155+
SET DEBUG_SYNC='now SIGNAL go';
156+
SET DEBUG_SYNC='now WAIT_FOR latches_released';
157+
SET GLOBAL innodb_purge_run_now = ON;
158+
SET DEBUG_SYNC='now SIGNAL go';
159+
connection con1;
160+
# Reap ALTER TABLE.
161+
SET SESSION debug="-d,pcursor_move_to_next_block_release_latches";
162+
# Ensure all data is present after index rebuild.
163+
# Test cleanup.
164+
connection default;
165+
DROP TABLE t1;
166+
SET GLOBAL innodb_limit_optimistic_insert_debug=0;
135167
#
136168
# Cleanup.
169+
connection default;
137170
DROP TABLE IF EXISTS t1;
138171
Warnings:
139172
Note 1051 Unknown table 'test.t1'
173+
DROP PROCEDURE test.insert_records;
140174
disconnect con1;

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

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,23 @@
44
--source include/have_debug.inc
55
--source include/have_debug_sync.inc
66

7-
SET DEBUG_SYNC= 'RESET';
87
--enable_connect_log
98
--connect (con1, localhost, root,,)
109

10+
--DELIMITER //
11+
12+
CREATE PROCEDURE test.insert_records (IN record_count INT)
13+
BEGIN
14+
DECLARE counter INT DEFAULT 1;
15+
16+
WHILE counter <= record_count DO
17+
INSERT INTO test.t1 VALUES (0);
18+
SET counter = counter + 1;
19+
END WHILE;
20+
END //
21+
22+
--DELIMITER ;
23+
1124
--echo ##############################################################
1225
--echo # Bug#36846567 Inplace ALTER TABLE might cause lost rows if concurrent purge
1326
--echo ##############################################################
@@ -21,7 +34,7 @@ SET GLOBAL INNODB_PURGE_STOP_NOW=ON;
2134
DELETE FROM t1 WHERE pk = 'bbbcc';
2235

2336
--connection con1
24-
SET DEBUG='+d,ddl_buf_add_two';
37+
SET SESSION DEBUG='+d,ddl_buf_add_two';
2538
SET DEBUG_SYNC='ddl_bulk_inserter_latches_released SIGNAL latches_released WAIT_FOR go';
2639
--echo # Send ALTER TABLE INPLACE which rebuilds table.
2740
--send ALTER TABLE t1 ENGINE=InnoDB, ALGORITHM=INPLACE
@@ -35,15 +48,13 @@ SET DEBUG_SYNC='now SIGNAL go';
3548
--connection con1
3649
--echo # Reap ALTER TABLE
3750
--reap
51+
SET SESSION DEBUG='-d,ddl_buf_add_two';
3852

3953
--echo # Before the fix row 'ddddd' was missing from the table after ALTER.
4054
SELECT * FROM t1;
4155

42-
SET DEBUG_SYNC= 'RESET';
43-
4456
--echo # Test cleanup
4557
--connection default
46-
SET DEBUG_SYNC= 'RESET';
4758
DROP TABLE t1;
4859

4960
--echo ##############################################################
@@ -60,7 +71,7 @@ SET GLOBAL INNODB_PURGE_STOP_NOW=ON;
6071
DELETE FROM t1 WHERE pk = 'aaaaa';
6172

6273
--connection con1
63-
SET DEBUG='+d,ddl_buf_add_two';
74+
SET SESSION DEBUG='+d,ddl_buf_add_two';
6475
SET DEBUG_SYNC='ddl_bulk_inserter_latches_released SIGNAL latches_released WAIT_FOR go';
6576
--echo # Send ALTER TABLE INPLACE which rebuilds table.
6677
--send ALTER TABLE t1 ENGINE=InnoDB, ALGORITHM=INPLACE
@@ -74,15 +85,13 @@ SET DEBUG_SYNC='now SIGNAL go';
7485
--connection con1
7586
--echo # Reap ALTER TABLE
7687
--reap
88+
SET SESSION DEBUG='-d,ddl_buf_add_two';
7789

7890
--echo # Verify intended records are present
7991
SELECT * FROM t1;
8092

81-
SET DEBUG_SYNC= 'RESET';
82-
8393
--echo # Test cleanup
8494
--connection default
85-
SET DEBUG_SYNC= 'RESET';
8695
DROP TABLE t1;
8796

8897
--echo ##############################################################
@@ -98,7 +107,7 @@ SET GLOBAL INNODB_PURGE_STOP_NOW=ON;
98107
DELETE FROM t1 WHERE pk = 'bbbbb';
99108

100109
--connection con1
101-
SET DEBUG='+d,ddl_buf_add_two';
110+
SET SESSION DEBUG='+d,ddl_buf_add_two';
102111
SET DEBUG_SYNC='ddl_bulk_inserter_latches_released SIGNAL latches_released WAIT_FOR go';
103112
--echo # Send ALTER TABLE INPLACE which rebuilds table.
104113
--send ALTER TABLE t1 ENGINE=InnoDB, ALGORITHM=INPLACE
@@ -112,15 +121,13 @@ SET DEBUG_SYNC='now SIGNAL go';
112121
--connection con1
113122
--echo # Reap ALTER TABLE
114123
--reap
124+
SET SESSION DEBUG='-d,ddl_buf_add_two';
115125

116126
--echo # Verify intended records are present
117127
SELECT * FROM t1;
118128

119-
SET DEBUG_SYNC= 'RESET';
120-
121129
--echo # Test cleanup
122130
--connection default
123-
SET DEBUG_SYNC= 'RESET';
124131
DROP TABLE t1;
125132

126133
--echo ##############################################################
@@ -136,7 +143,7 @@ SET GLOBAL INNODB_PURGE_STOP_NOW=ON;
136143
DELETE FROM t1 WHERE pk = 'ccccc';
137144

138145
--connection con1
139-
SET DEBUG='+d,ddl_buf_add_two';
146+
SET SESSION DEBUG='+d,ddl_buf_add_two';
140147
SET DEBUG_SYNC='ddl_bulk_inserter_latches_released SIGNAL latches_released WAIT_FOR go';
141148
--echo # Send ALTER TABLE INPLACE which rebuilds table.
142149
--send ALTER TABLE t1 ENGINE=InnoDB, ALGORITHM=INPLACE
@@ -150,20 +157,88 @@ SET DEBUG_SYNC='now SIGNAL go';
150157
--connection con1
151158
--echo # Reap ALTER TABLE
152159
--reap
160+
SET SESSION DEBUG='-d,ddl_buf_add_two';
153161

154162
--echo # Verify intended records are present
155163
SELECT * FROM t1;
156164

157-
SET DEBUG_SYNC= 'RESET';
158-
159165
--echo # Test cleanup
160166
--connection default
161-
SET DEBUG_SYNC= 'RESET';
162167
DROP TABLE t1;
163168

169+
--echo ############################################################
170+
--echo # Test#5 Bug#37318367 Delete part of records from two pages
171+
--echo # to lose a record - pause in PCursor::move_to_next_block
172+
--echo ############################################################
173+
174+
# Limit the max number of records per page to 3
175+
SET GLOBAL innodb_limit_optimistic_insert_debug=3;
176+
177+
--connection default
178+
CREATE TABLE t1 (id int AUTO_INCREMENT NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
179+
180+
# 8 records are inserted into four pages as following due to
181+
# sysvar innodb_limit_optimistic_insert_debug=3 set above :
182+
# p1: {1}, p2: {2,3,4}, p3: {5,6,7}, p4: {8}
183+
CALL insert_records(8);
184+
185+
SET GLOBAL innodb_purge_stop_now = ON;
186+
187+
# Remove records in right half of page p2 and left half of page p3
188+
DELETE FROM t1 WHERE id > 2 AND id < 7;
189+
190+
let $before_count = `SELECT COUNT(*) FROM t1`;
191+
--connection con1
192+
SET DEBUG_SYNC='pcursor_move_to_next_block_latches_released SIGNAL latches_released WAIT_FOR go EXECUTE 2';
193+
194+
--echo # Send ALTER TABLE INPLACE to rebuild the index.
195+
196+
# Trick the PCursor::move_to_next_block to think the latch is congested
197+
SET SESSION debug="+d,pcursor_move_to_next_block_release_latches";
198+
# Save the cursor position at the end of record#4 and wait for the second
199+
# 'go' signal to resume operation.
200+
--send ALTER TABLE t1 ENGINE=InnoDB, ALGORITHM=INPLACE
201+
202+
--connection default
203+
# wait for the first hit of the sync point at the end of first page ...
204+
SET DEBUG_SYNC='now WAIT_FOR latches_released';
205+
# ... which we ignore by immediately resuming the alter table operation
206+
SET DEBUG_SYNC='now SIGNAL go';
207+
# Now wait for the second hit of the sync point at the end of second page
208+
SET DEBUG_SYNC='now WAIT_FOR latches_released';
209+
SET GLOBAL innodb_purge_run_now = ON;
210+
--source include/wait_innodb_all_purged.inc
211+
212+
# Resume the alter table operation after page p2 is processed. Since the purge
213+
# is completed so remaining records are arranged as following :
214+
# p1: {1, 2, 7}, p4: {8}
215+
# Without the fix, cursor is positioned to record#8 instead of record#7 thus
216+
# latter is skipped.
217+
SET DEBUG_SYNC='now SIGNAL go';
218+
219+
--connection con1
220+
--echo # Reap ALTER TABLE.
221+
--reap;
222+
SET SESSION debug="-d,pcursor_move_to_next_block_release_latches";
223+
224+
--echo # Ensure all data is present after index rebuild.
225+
let $after_count = `SELECT COUNT(*) FROM t1`;
226+
if($before_count != $after_count) {
227+
--echo # Records, before rebuild=$before_count != after rebuild=$after_count"
228+
SELECT id FROM t1;
229+
--die "Test failed due to record count mismatch after index rebuild"
230+
}
231+
232+
--echo # Test cleanup.
233+
--connection default
234+
DROP TABLE t1;
235+
SET GLOBAL innodb_limit_optimistic_insert_debug=0;
236+
164237
--echo #
165238
--echo # Cleanup.
239+
--connection default
166240
DROP TABLE IF EXISTS t1;
241+
DROP PROCEDURE test.insert_records;
167242
--disconnect con1
168243
--disable_connect_log
169244
# Wait till all disconnects are completed

0 commit comments

Comments
 (0)