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';
43 changes: 43 additions & 0 deletions mysql-test/suite/innodb/r/old_blob.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#
# 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)));
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)));
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';
connection purge_control;
COMMIT;
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
connection default;
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;
53 changes: 53 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,53 @@
#
# 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)));
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';
connection purge_control;
COMMIT;
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;
65 changes: 65 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,65 @@
#
# 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)));
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';
connection purge_control;
COMMIT;
disconnect purge_control;
connection default;
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;
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;
80 changes: 80 additions & 0 deletions mysql-test/suite/innodb/t/old_blob.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
--source include/have_innodb.inc
--source include/have_innodb_max_16k.inc
--source include/have_debug_sync.inc

--echo #
--echo # MDEV-38056 An implicit-lock check must keep purge_sys.view frozen across
--echo # the dereference of an externally stored column of a version it rebuilt.
--echo # The transaction it references commits meanwhile, deregistering itself
--echo # from trx_sys before it waits for the reader's reference, which makes its
--echo # history purgeable under the reader.
--echo #

--source include/wait_all_purged.inc

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)));

# Filler rows keep the delete-marked entries off the leaf page holding the live
# 'z...' entry, which the reader latches; purge would block on it otherwise.
--disable_query_log
let $i= 60;
while ($i)
{
eval INSERT INTO t VALUES (100 + $i,
CONCAT('y~', LPAD($i, 3, '0'), REPEAT('-', 800)));
dec $i;
}
--enable_query_log

# The counter is cumulative over the lifetime of the server.
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;

# The locking read references the still active transaction above and parks after
# building the previous version, before dereferencing its BLOB pointers.
connect (reader,localhost,root,,);
SET DEBUG_SYNC='row_vers_impl_x_locked_row_build SIGNAL parked WAIT_FOR resume';
--send SELECT a FROM t FORCE INDEX(b) WHERE b LIKE 'z%' FOR UPDATE

connection default;
SET DEBUG_SYNC='now WAIT_FOR parked TIMEOUT 60';
--send COMMIT

# Both old BLOBs must still be allocated while the reader is parked on them.
connect (con3,localhost,root,,);
--source suite/innodb/include/assert_blob_not_purged.inc
SET DEBUG_SYNC='now SIGNAL resume';

connection reader;
--reap

connection default;
--reap
disconnect reader;

# Once the reader is done, purge advances, which confirms that only the frozen
# view held it back.
connection con3;
let $wait_condition=
SELECT count >= 2 FROM information_schema.innodb_metrics
WHERE name = 'purge_upd_exist_or_extern_records';
--source include/wait_condition.inc

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';
--disable_warnings
SET GLOBAL innodb_monitor_enable=default;
SET GLOBAL innodb_monitor_disable=default;
SET GLOBAL innodb_monitor_reset_all=default;
--enable_warnings
Loading