Skip to content

Commit 0de3be8

Browse files
committed
MDEV-30671 InnoDB undo log truncation fails to wait for purge of history
It is not safe to invoke trx_purge_free_segment() or execute innodb_undo_log_truncate=ON before all undo log records in the rollback segment has been processed. A prominent failure that would occur due to premature freeing of undo log pages is that trx_undo_get_undo_rec() would crash when trying to copy an undo log record to fetch the previous version of a record. If trx_undo_get_undo_rec() was not invoked in the unlucky time frame, then the symptom would be that some committed transaction history is never removed. This would be detected by CHECK TABLE...EXTENDED that was impleented in commit ab01901. Such a garbage collection leak should be possible even when using innodb_undo_log_truncate=OFF, just involving trx_purge_free_segment(). trx_rseg_t::needs_purge: Change the type from Boolean to a transaction identifier, noting the most recent non-purged transaction, or 0 if everything has been purged. On transaction start, we initialize this to 1 more than the transaction start ID. On recovery, the field may be adjusted to the transaction end ID (TRX_UNDO_TRX_NO) if it is larger. The field TRX_UNDO_NEEDS_PURGE becomes write-only; only some debug assertions that would validate the value. The field reflects the old inaccurate Boolean field trx_rseg_t::needs_purge. trx_undo_mem_create_at_db_start(), trx_undo_lists_init(), trx_rseg_mem_restore(): Remove the parameter max_trx_id. Instead, store the maximum in trx_rseg_t::needs_purge, where trx_rseg_array_init() will find it. trx_purge_free_segment(): Contiguously hold a lock on trx_rseg_t to prevent any concurrent allocation of undo log. trx_purge_truncate_rseg_history(): Only invoke trx_purge_free_segment() if the rollback segment is empty and there are no pending transactions associated with it. trx_purge_truncate_history(): Only proceed with innodb_undo_log_truncate=ON if trx_rseg_t::needs_purge indicates that all history has been purged. Tested by: Matthias Leich
1 parent d3f35aa commit 0de3be8

16 files changed

+206
-200
lines changed

mysql-test/suite/gcol/r/gcol_purge.result

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
SET @save_frequency=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
2+
SET @save_dbug=@@GLOBAL.debug_dbug;
3+
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
14
CREATE TABLE t1(f1 INT NOT NULL, f2 int not null,
25
f3 int generated always as (f2 * 2) VIRTUAL,
36
primary key(f1), INDEX (f3))ENGINE=InnoDB;
47
connect con1,localhost,root,,,;
8+
InnoDB 0 transactions not purged
59
START TRANSACTION WITH CONSISTENT SNAPSHOT;
610
connection default;
711
INSERT INTO t1(f1, f2) VALUES(1,2);
@@ -18,5 +22,6 @@ commit;
1822
disconnect con1;
1923
disconnect con2;
2024
connection default;
21-
set global debug_dbug=default;
25+
SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_frequency;
26+
SET GLOBAL debug_dbug=@save_dbug;
2227
DROP TABLE t1;

mysql-test/suite/gcol/t/gcol_purge.test

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
--source include/have_innodb.inc
22
--source include/have_debug.inc
3+
4+
SET @save_frequency=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
5+
SET @save_dbug=@@GLOBAL.debug_dbug;
6+
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
37
CREATE TABLE t1(f1 INT NOT NULL, f2 int not null,
48
f3 int generated always as (f2 * 2) VIRTUAL,
59
primary key(f1), INDEX (f3))ENGINE=InnoDB;
610
connect(con1,localhost,root,,,);
11+
--source ../innodb/include/wait_all_purged.inc
712
START TRANSACTION WITH CONSISTENT SNAPSHOT;
813

914
connection default;
@@ -26,5 +31,6 @@ commit;
2631
disconnect con1;
2732
disconnect con2;
2833
connection default;
29-
set global debug_dbug=default;
34+
SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_frequency;
35+
SET GLOBAL debug_dbug=@save_dbug;
3036
DROP TABLE t1;

mysql-test/suite/innodb/r/cursor-restore-locking.result

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
SET @save_freq=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
2+
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
13
CREATE TABLE t (a int PRIMARY KEY, b int NOT NULL UNIQUE) engine = InnoDB;
4+
InnoDB 0 transactions not purged
25
connect prevent_purge,localhost,root,,;
36
start transaction with consistent snapshot;
47
connect con_del_1,localhost,root,,;
@@ -34,3 +37,4 @@ disconnect con_del_2;
3437
connection default;
3538
SET DEBUG_SYNC = 'RESET';
3639
DROP TABLE t;
40+
SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_freq;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;
77
SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;
88
CREATE TABLE t1(a INT PRIMARY KEY, b INT NOT NULL)
99
ROW_FORMAT=REDUNDANT ENGINE=InnoDB;
10+
InnoDB 0 transactions not purged
1011
connect prevent_purge,localhost,root;
1112
START TRANSACTION WITH CONSISTENT SNAPSHOT;
1213
connection default;
@@ -19,7 +20,11 @@ UPDATE t1 SET b=4 WHERE a=3;
1920
disconnect prevent_purge;
2021
connection default;
2122
InnoDB 0 transactions not purged
23+
connection con1;
24+
ROLLBACK;
2225
disconnect con1;
26+
connection default;
27+
InnoDB 0 transactions not purged
2328
FLUSH TABLE t1 FOR EXPORT;
2429
Clustered index root page contents:
2530
N_RECS=3; LEVEL=0

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
33
CREATE TABLE t1(id INT PRIMARY key, val VARCHAR(16000)) ENGINE=InnoDB;
44
INSERT INTO t1 (id,val) SELECT 2*seq,'x' FROM seq_0_to_1023;
55
connect con1,localhost,root,,;
6+
InnoDB 0 transactions not purged
67
START TRANSACTION WITH CONSISTENT SNAPSHOT;
78
connection default;
89
DELETE FROM t1 WHERE id=1788;

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ SET GLOBAL innodb_purge_rseg_truncate_frequency= 1;
99
CREATE PROCEDURE insert_n(start int, end int)
1010
BEGIN
1111
DECLARE i INT DEFAULT start;
12-
START TRANSACTION;
1312
WHILE i <= end do
1413
INSERT INTO t1 VALUES (1, 2, 3) ON DUPLICATE KEY UPDATE c = i;
1514
SET i = i + 1;
1615
END WHILE;
17-
COMMIT;
1816
END~~
1917
CREATE FUNCTION num_pages_get()
2018
RETURNS INT
@@ -29,7 +27,8 @@ END~~
2927
# Create a table with one record in it and start an RR transaction
3028
#
3129
CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,b), KEY (b,c))
32-
ENGINE=InnoDB;
30+
ENGINE=InnoDB STATS_PERSISTENT=0;
31+
InnoDB 0 transactions not purged
3332
BEGIN;
3433
SELECT * FROM t1;
3534
a b c
@@ -38,20 +37,24 @@ a b c
3837
#
3938
connect con2, localhost, root,,;
4039
connection con2;
40+
BEGIN;
4141
INSERT INTO t1 VALUES (1, 2, 3) ON DUPLICATE KEY UPDATE c = NULL;
4242
CALL insert_n(1, 50);;
4343
connect con3, localhost, root,,;
4444
connection con3;
45+
BEGIN;
4546
CALL insert_n(51, 100);;
4647
connection con2;
48+
COMMIT;
4749
connection con3;
4850
INSERT INTO t1 VALUES (1, 2, 1) ON DUPLICATE KEY UPDATE c = NULL;
51+
COMMIT;
4952
connection default;
5053
#
5154
# Connect to default and record how many pages were accessed
5255
# when selecting the record using the secondary key.
5356
#
54-
InnoDB 4 transactions not purged
57+
InnoDB 2 transactions not purged
5558
SET @num_pages_1 = num_pages_get();
5659
SELECT * FROM t1 force index (b);
5760
a b c

mysql-test/suite/innodb/t/cursor-restore-locking.test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
source include/have_debug.inc;
44
source include/have_debug_sync.inc;
55

6+
SET @save_freq=@@GLOBAL.innodb_purge_rseg_truncate_frequency;
7+
SET GLOBAL innodb_purge_rseg_truncate_frequency=1;
68
CREATE TABLE t (a int PRIMARY KEY, b int NOT NULL UNIQUE) engine = InnoDB;
79

10+
--source include/wait_all_purged.inc
811
--connect(prevent_purge,localhost,root,,)
912
start transaction with consistent snapshot;
1013

@@ -80,4 +83,5 @@ INSERT INTO t VALUES(30, 20);
8083

8184
SET DEBUG_SYNC = 'RESET';
8285
DROP TABLE t;
86+
SET GLOBAL innodb_purge_rseg_truncate_frequency=@save_freq;
8387
--source include/wait_until_count_sessions.inc

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;
1414

1515
CREATE TABLE t1(a INT PRIMARY KEY, b INT NOT NULL)
1616
ROW_FORMAT=REDUNDANT ENGINE=InnoDB;
17+
--source include/wait_all_purged.inc
1718

1819
--connect (prevent_purge,localhost,root)
1920
START TRANSACTION WITH CONSISTENT SNAPSHOT;
@@ -33,7 +34,12 @@ UPDATE t1 SET b=4 WHERE a=3;
3334
# Initiate a full purge, which should reset the DB_TRX_ID except for a=3.
3435
--source include/wait_all_purged.inc
3536
# Initiate a ROLLBACK of the update, which should reset the DB_TRX_ID for a=3.
37+
--connection con1
38+
ROLLBACK;
3639
--disconnect con1
40+
--connection default
41+
# Reset the DB_TRX_ID for the hidden ADD COLUMN metadata record.
42+
--source include/wait_all_purged.inc
3743

3844
FLUSH TABLE t1 FOR EXPORT;
3945
# The following is based on innodb.table_flags:

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CREATE TABLE t1(id INT PRIMARY key, val VARCHAR(16000)) ENGINE=InnoDB;
99
INSERT INTO t1 (id,val) SELECT 2*seq,'x' FROM seq_0_to_1023;
1010

1111
connect(con1,localhost,root,,);
12+
source include/wait_all_purged.inc;
1213
# Prevent purge.
1314
START TRANSACTION WITH CONSISTENT SNAPSHOT;
1415
connection default;

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ DELIMITER ~~;
1313
CREATE PROCEDURE insert_n(start int, end int)
1414
BEGIN
1515
DECLARE i INT DEFAULT start;
16-
START TRANSACTION;
1716
WHILE i <= end do
1817
INSERT INTO t1 VALUES (1, 2, 3) ON DUPLICATE KEY UPDATE c = i;
1918
SET i = i + 1;
2019
END WHILE;
21-
COMMIT;
2220
END~~
2321

2422
CREATE FUNCTION num_pages_get()
@@ -36,7 +34,8 @@ DELIMITER ;~~
3634
--echo # Create a table with one record in it and start an RR transaction
3735
--echo #
3836
CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY(a,b), KEY (b,c))
39-
ENGINE=InnoDB;
37+
ENGINE=InnoDB STATS_PERSISTENT=0;
38+
--source include/wait_all_purged.inc
4039
BEGIN;
4140
SELECT * FROM t1;
4241

@@ -45,26 +44,30 @@ SELECT * FROM t1;
4544
--echo #
4645
connect (con2, localhost, root,,);
4746
connection con2;
47+
BEGIN;
4848
INSERT INTO t1 VALUES (1, 2, 3) ON DUPLICATE KEY UPDATE c = NULL;
4949
--send CALL insert_n(1, 50);
5050

5151
connect (con3, localhost, root,,);
5252
connection con3;
53+
BEGIN;
5354
--send CALL insert_n(51, 100);
5455

5556
connection con2;
5657
reap;
58+
COMMIT;
5759
connection con3;
5860
reap;
5961
INSERT INTO t1 VALUES (1, 2, 1) ON DUPLICATE KEY UPDATE c = NULL;
62+
COMMIT;
6063

6164
connection default;
6265

6366
--echo #
6467
--echo # Connect to default and record how many pages were accessed
6568
--echo # when selecting the record using the secondary key.
6669
--echo #
67-
--let $wait_all_purged=4
70+
--let $wait_all_purged=2
6871
--source include/wait_all_purged.inc
6972
SET @num_pages_1 = num_pages_get();
7073
SELECT * FROM t1 force index (b);

0 commit comments

Comments
 (0)