Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions mysql-test/suite/innodb/include/assert_blob_not_purged.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Assert that purge has not freed the BLOB while a version-chain walk is parked
# at the dereference of its reference. Purging an update record is what would
# free it; the caller must have enabled and reset that monitor, the counter
# being cumulative over the lifetime of the server.
#
# The wait gives purge every chance to get there first, which is what makes the
# assertion fail rather than pass by accident; without the freeze it needs about
# a second. It cannot be a wait_condition, because a frozen purge_sys.view is
# precisely what stops every purge counter from moving.

--disable_query_log
let $purge_wait_rounds= 20;
while ($purge_wait_rounds)
{
let $freed= `SELECT count FROM information_schema.innodb_metrics WHERE name = 'purge_upd_exist_or_extern_records'`;
if ($freed)
{
let $purge_wait_rounds= 0;
}
if (!$freed)
{
real_sleep 0.1;
dec $purge_wait_rounds;
}
}
--enable_query_log
SELECT count AS upd_records_purged_while_parked FROM information_schema.innodb_metrics
WHERE name = 'purge_upd_exist_or_extern_records';
46 changes: 46 additions & 0 deletions mysql-test/suite/innodb/r/old_blob.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#
# MDEV-38056 An implicit-lock check must keep purge_sys.view frozen across
# the dereference of an externally stored column of a version it rebuilt.
# The transaction it references commits meanwhile, deregistering itself
# from trx_sys before it waits for the reader's reference, which makes its
# history purgeable under the reader.
#
InnoDB 0 transactions not purged
CREATE TABLE t(a INT PRIMARY KEY, b TEXT, UNIQUE(b(700)))
ENGINE=InnoDB STATS_PERSISTENT=0 ROW_FORMAT=COMPRESSED;
INSERT INTO t VALUES (1, CONCAT('x', REPEAT('x', @@innodb_page_size)));
INSERT INTO t
SELECT 100 + seq, CONCAT('y~', LPAD(seq, 3, '0'), REPEAT('-', 800))
FROM seq_60_to_1;
SET GLOBAL innodb_monitor_reset_all='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_enable='purge_upd_exist_or_extern_records';
BEGIN;
UPDATE t SET b = CONCAT('y', REPEAT('y', @@innodb_page_size)) WHERE a = 1;
UPDATE t SET b = CONCAT('z', REPEAT('z', @@innodb_page_size)) WHERE a = 1;
connect reader,localhost,root,,;
SET DEBUG_SYNC='row_vers_impl_x_locked_row_build SIGNAL parked WAIT_FOR resume';
SELECT a FROM t FORCE INDEX(b) WHERE b LIKE 'z%' FOR UPDATE;
connection default;
SET DEBUG_SYNC='now WAIT_FOR parked TIMEOUT 60';
COMMIT;
connect con3,localhost,root,,;
SELECT count AS upd_records_purged_while_parked FROM information_schema.innodb_metrics
WHERE name = 'purge_upd_exist_or_extern_records';
upd_records_purged_while_parked
0
SET DEBUG_SYNC='now SIGNAL resume';
connection reader;
a
1
connection default;
disconnect reader;
connection con3;
connection default;
disconnect con3;
SET DEBUG_SYNC='RESET';
DROP TABLE t;
SET GLOBAL innodb_monitor_disable='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_reset_all='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_enable=default;
SET GLOBAL innodb_monitor_disable=default;
SET GLOBAL innodb_monitor_reset_all=default;
57 changes: 57 additions & 0 deletions mysql-test/suite/innodb/r/old_blob_check.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#
# MDEV-38056 CHECK TABLE ... EXTENDED must keep purge_sys.view frozen
# across the dereference of an externally stored column of a version it
# rebuilt. It reaches versions that purge_sys.view already permits to be
# freed, deciding reachability from the lagging purge_sys.end_view.
#
InnoDB 0 transactions not purged
CREATE TABLE t(a INT PRIMARY KEY, b TEXT, UNIQUE(b(700)))
ENGINE=InnoDB STATS_PERSISTENT=0 ROW_FORMAT=COMPRESSED;
INSERT INTO t VALUES (1, CONCAT('z', REPEAT('z', @@innodb_page_size)));
INSERT INTO t
SELECT 100 + seq, CONCAT('y~', LPAD(seq, 3, '0'), REPEAT('-', 800))
FROM seq_60_to_1;
SET GLOBAL innodb_monitor_reset_all='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_enable='purge_upd_exist_or_extern_records';
connect purge_control,localhost,root,,;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
UPDATE t SET b = CONCAT('y', REPEAT('y', @@innodb_page_size)) WHERE a = 1;
connect victim,localhost,root,,;
BEGIN;
UPDATE t SET b = CONCAT('x', REPEAT('x', @@innodb_page_size)) WHERE a = 1;
connect checker,localhost,root,,;
SET DEBUG_SYNC='row_check_index_extended_match SIGNAL parked WAIT_FOR resume EXECUTE 2';
CHECK TABLE t EXTENDED;
connection default;
SET DEBUG_SYNC='now WAIT_FOR parked TIMEOUT 60';
SET DEBUG_SYNC='now SIGNAL resume';
SET DEBUG_SYNC='now WAIT_FOR parked TIMEOUT 60';
disconnect purge_control;
connection default;
SELECT count AS upd_records_purged_while_parked FROM information_schema.innodb_metrics
WHERE name = 'purge_upd_exist_or_extern_records';
upd_records_purged_while_parked
0
SET DEBUG_SYNC='now SIGNAL resume';
connection checker;
Table Op Msg_type Msg_text
test.t check status OK
disconnect checker;
connection victim;
COMMIT;
connection default;
disconnect victim;
SET DEBUG_SYNC='RESET';
SELECT a FROM t FORCE INDEX(b) WHERE b LIKE 'xx%';
a
1
CHECK TABLE t EXTENDED;
Table Op Msg_type Msg_text
test.t check status OK
DROP TABLE t;
SET GLOBAL innodb_monitor_disable='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_reset_all='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_enable=default;
SET GLOBAL innodb_monitor_disable=default;
SET GLOBAL innodb_monitor_reset_all=default;
90 changes: 90 additions & 0 deletions mysql-test/suite/innodb/r/old_blob_purgeable.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#
# MDEV-38056 Each of the three walks that freeze purge_sys.view across the
# dereference of an externally stored column re-establishes under the
# freeze that the version it rebuilt is still reachable. A debug keyword
# forces each of those tests to fail, so that the exit it guards is
# reachable without the race that produces it.
#
InnoDB 0 transactions not purged
SET @old_dbug= @@SESSION.debug_dbug;
#
# row_vers_impl_x_locked_purgeable: report no implicit lock.
#
CREATE TABLE t(a INT PRIMARY KEY, b TEXT, UNIQUE(b(700)))
ENGINE=InnoDB STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC;
INSERT INTO t VALUES (1, REPEAT('x', @@innodb_page_size));
BEGIN;
UPDATE t SET b = REPEAT('y', @@innodb_page_size) WHERE a = 1;
UPDATE t SET b = REPEAT('z', @@innodb_page_size) WHERE a = 1;
connect reader,localhost,root,,;
SET @old_dbug= @@SESSION.debug_dbug;
SET SESSION debug_dbug='+d,row_vers_impl_x_locked_purgeable';
SELECT a FROM t FORCE INDEX(b) WHERE b LIKE 'z%' FOR UPDATE SKIP LOCKED;
a
SET SESSION debug_dbug= @old_dbug;
connection default;
COMMIT;
connection reader;
SELECT a FROM t FORCE INDEX(b) WHERE b LIKE 'z%' FOR UPDATE SKIP LOCKED;
a
1
COMMIT;
connection default;
CHECK TABLE t EXTENDED;
Table Op Msg_type Msg_text
test.t check status OK
DROP TABLE t;
#
# row_undo_mod_sec_is_unsafe_purgeable: report the secondary index entry
# as still needed, which only makes the rollback delete-mark an entry it
# could have removed.
#
CREATE TABLE t(a INT PRIMARY KEY, pad INT, b TEXT, UNIQUE(b(700)))
ENGINE=InnoDB STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC;
INSERT INTO t VALUES (1, 0, REPEAT('x', @@innodb_page_size));
UPDATE t SET pad = 1 WHERE a = 1;
UPDATE t SET b = REPEAT('y', @@innodb_page_size) WHERE a = 1;
BEGIN;
UPDATE t SET b = REPEAT('z', @@innodb_page_size) WHERE a = 1;
SET SESSION debug_dbug='+d,row_undo_mod_sec_is_unsafe_purgeable';
ROLLBACK;
SET SESSION debug_dbug= @old_dbug;
SELECT a, pad, LEFT(b, 2) FROM t FORCE INDEX(b) WHERE b LIKE 'yy%';
a pad LEFT(b, 2)
1 1 yy
CHECK TABLE t EXTENDED;
Table Op Msg_type Msg_text
test.t check status OK
DROP TABLE t;
#
# row_check_index_purgeable: stop the walk and report nothing.
#
CREATE TABLE t(a INT PRIMARY KEY, b TEXT, UNIQUE(b(700)))
ENGINE=InnoDB STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC;
INSERT INTO t VALUES (1, REPEAT('x', @@innodb_page_size));
connect purge_control,localhost,root,,;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
UPDATE t SET b = 's' WHERE a = 1;
connect victim,localhost,root,,;
BEGIN;
UPDATE t SET b = 'q' WHERE a = 1;
connection default;
SET SESSION debug_dbug='+d,row_check_index_purgeable';
CHECK TABLE t EXTENDED;
Table Op Msg_type Msg_text
test.t check status OK
SET SESSION debug_dbug= @old_dbug;
CHECK TABLE t EXTENDED;
Table Op Msg_type Msg_text
test.t check status OK
connection victim;
ROLLBACK;
disconnect purge_control;
connection default;
disconnect reader;
disconnect victim;
CHECK TABLE t EXTENDED;
Table Op Msg_type Msg_text
test.t check status OK
DROP TABLE t;
54 changes: 54 additions & 0 deletions mysql-test/suite/innodb/r/old_blob_rollback.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#
# MDEV-38056 A rollback must keep purge_sys.view frozen across the
# dereference of an externally stored column of a version it rebuilt. It
# froze that view only inside trx_undo_prev_version_build(), so purge was
# free to release the BLOB pages before the caller fetched them.
#
InnoDB 0 transactions not purged
CREATE TABLE t(a INT PRIMARY KEY, pad INT, b TEXT, UNIQUE(b(700)))
ENGINE=InnoDB STATS_PERSISTENT=0 ROW_FORMAT=COMPRESSED;
INSERT INTO t VALUES (1, 0, CONCAT('x', REPEAT('x', @@innodb_page_size)));
INSERT INTO t
SELECT 100 + seq, 0, CONCAT('y~', LPAD(seq, 3, '0'), REPEAT('-', 800))
FROM seq_60_to_1;
SET GLOBAL innodb_monitor_reset_all='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_enable='purge_upd_exist_or_extern_records';
connect purge_control,localhost,root,,;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
UPDATE t SET pad = 1 WHERE a = 1;
UPDATE t SET b = CONCAT('y', REPEAT('y', @@innodb_page_size)) WHERE a = 1;
connect victim,localhost,root,,;
BEGIN;
UPDATE t SET b = CONCAT('z', REPEAT('z', @@innodb_page_size)) WHERE a = 1;
SET DEBUG_SYNC='row_undo_mod_sec_is_unsafe_row_build SIGNAL parked WAIT_FOR resume EXECUTE 3';
ROLLBACK;
connection default;
SET DEBUG_SYNC='now WAIT_FOR parked TIMEOUT 60';
SET DEBUG_SYNC='now SIGNAL resume';
SET DEBUG_SYNC='now WAIT_FOR parked TIMEOUT 60';
SET DEBUG_SYNC='now SIGNAL resume';
SET DEBUG_SYNC='now WAIT_FOR parked TIMEOUT 60';
disconnect purge_control;
connection default;
SELECT count AS upd_records_purged_while_parked FROM information_schema.innodb_metrics
WHERE name = 'purge_upd_exist_or_extern_records';
upd_records_purged_while_parked
0
SET DEBUG_SYNC='now SIGNAL resume';
connection victim;
connection default;
disconnect victim;
SET DEBUG_SYNC='RESET';
SELECT a, pad FROM t FORCE INDEX(b) WHERE b LIKE 'yy%';
a pad
1 1
CHECK TABLE t;
Table Op Msg_type Msg_text
test.t check status OK
DROP TABLE t;
SET GLOBAL innodb_monitor_disable='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_reset_all='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_enable=default;
SET GLOBAL innodb_monitor_disable=default;
SET GLOBAL innodb_monitor_reset_all=default;
72 changes: 72 additions & 0 deletions mysql-test/suite/innodb/r/old_blob_updel.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#
# MDEV-38056 As innodb.old_blob, but the version is rebuilt from the undo
# log record of an update of a record that another, committed transaction
# delete-marked. Such a record stores only the 20-byte reference, so the
# frozen purge_sys.view is the only thing that can keep the dereference
# valid. Reaching that version needs a batch to be still in progress.
#
InnoDB 0 transactions not purged
CREATE TABLE t(a INT PRIMARY KEY, b TEXT, UNIQUE(b(700)))
ENGINE=InnoDB STATS_PERSISTENT=0 ROW_FORMAT=COMPRESSED;
INSERT INTO t VALUES (1, CONCAT('x', REPEAT('x', @@innodb_page_size)));
INSERT INTO t
SELECT 100 + seq, CONCAT('y~', LPAD(seq, 3, '0'), REPEAT('-', 800))
FROM seq_60_to_1;
SET GLOBAL innodb_monitor_reset_all='purge_del_mark_records';
SET GLOBAL innodb_monitor_reset_all='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_enable='purge_del_mark_records';
SET GLOBAL innodb_monitor_enable='purge_upd_exist_or_extern_records';
connect purge_control,localhost,root,,;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
DELETE FROM t WHERE a = 1;
connect victim,localhost,root,,;
BEGIN;
INSERT INTO t VALUES (1, CONCAT('z', REPEAT('z', @@innodb_page_size)));
connection default;
SET @old_dbug= @@GLOBAL.debug_dbug;
SET GLOBAL debug_dbug='+d,purge_hold_cleanup';
disconnect purge_control;
connection default;
SET DEBUG_SYNC='now WAIT_FOR purge_batch_parked TIMEOUT 60';
SELECT count > 0 AS del_mark_records_purged
FROM information_schema.innodb_metrics WHERE name = 'purge_del_mark_records';
del_mark_records_purged
1
connect reader,localhost,root,,;
SET DEBUG_SYNC='row_vers_impl_x_locked_row_build SIGNAL parked WAIT_FOR resume';
SELECT a FROM t FORCE INDEX(b) WHERE b LIKE 'z%' FOR UPDATE;
connection default;
SET DEBUG_SYNC='now WAIT_FOR parked TIMEOUT 60';
SET GLOBAL debug_dbug= @old_dbug;
SET DEBUG_SYNC='now SIGNAL purge_batch_resume';
connection victim;
COMMIT;
connection default;
SELECT count AS upd_records_purged_while_parked FROM information_schema.innodb_metrics
WHERE name = 'purge_upd_exist_or_extern_records';
upd_records_purged_while_parked
0
SET DEBUG_SYNC='now SIGNAL resume';
connection victim;
connection reader;
a
1
connection default;
disconnect reader;
disconnect victim;
SET DEBUG_SYNC='RESET';
SELECT a FROM t FORCE INDEX(b) WHERE b LIKE 'zz%';
a
1
CHECK TABLE t;
Table Op Msg_type Msg_text
test.t check status OK
DROP TABLE t;
SET GLOBAL innodb_monitor_disable='purge_del_mark_records';
SET GLOBAL innodb_monitor_disable='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_reset_all='purge_del_mark_records';
SET GLOBAL innodb_monitor_reset_all='purge_upd_exist_or_extern_records';
SET GLOBAL innodb_monitor_enable=default;
SET GLOBAL innodb_monitor_disable=default;
SET GLOBAL innodb_monitor_reset_all=default;
Loading