Commit 849af74a authored by Marko Mäkelä's avatar Marko Mäkelä

MariaDB adjustments for Oracle Bug#23070734 fix

Split the test case so that a server restart is not needed.
Reduce the test cases and use a simpler mechanism for triggering
and waiting for purge.

fil_table_accessible(): Check if a table can be accessed without
enjoying MDL protection.
parent 62dca454
......@@ -20,14 +20,11 @@ insert into t3 values (10,20),(30,40),(50,50);
insert into t3 select * from t3;
insert into t3 select * from t3;
SET session lock_wait_timeout = 1;
show variables like 'lock_wait_timeout%';
Variable_name Value
lock_wait_timeout 1
connection 1
SET DEBUG_SYNC= 'simulate_buffer_pool_scan SIGNAL opened WAIT_FOR finish_scan';
truncate table t1;;
connection default
SET DEBUG_SYNC= 'now WAIT_FOR opened';
connect con1,localhost,root,,;
SET DEBUG_SYNC= 'buffer_pool_scan SIGNAL started WAIT_FOR finish_scan';
truncate table t1;
connection default;
SET DEBUG_SYNC= 'now WAIT_FOR started';
Check Analyze table. Gives lock time out error.
analyze table t1;
Table Op Msg_type Msg_text
......@@ -47,7 +44,7 @@ alter table t1 add column f4 int;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
check if table can be created with the same name
create table t1 (bd int) engine=innodb;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
Got one of the listed errors
check if index can be created on table being truncated
create index idx1 on t1(f1);
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
......@@ -74,51 +71,16 @@ check if index can be created on the other table
create index idx1 on t2(f3);
Check if we can turn off persistent stats off entire instance
SET GLOBAL innodb_stats_persistent=off;
connection con2
set global innodb_adaptive_hash_index=off;;
connection default
connect con2,localhost,root,,;
set global innodb_adaptive_hash_index=off;
connection default;
SET DEBUG_SYNC= 'now SIGNAL finish_scan';
connection con1
connection con2
connection default
SET DEBUG_SYNC= 'RESET';
connection con1;
disconnect con1;
connection con2;
disconnect con2;
connection default;
SET session lock_wait_timeout=default;
set global innodb_adaptive_hash_index=default;
set global innodb_adaptive_hash_index=on;
drop table t1,t2,t3;
SET @@global.innodb_stats_persistent=default;
Test_2 :- Check if purge thread ignores the undo
log records of the table being truncated.
create table t1 (f1 int ,f2 int,key(f2)) engine=innodb;
insert into t1 values (10,45),(20,78),(30,88),(40,23),(50,78),(60,11),(70,56),(80,90);
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
select count(*) from t1;
count(*)
131072
Stop the purge thread
set global innodb_purge_stop_now = 1;
delete from t1;
connection con1
SET DEBUG_SYNC= 'simulate_buffer_pool_scan SIGNAL opened WAIT_FOR finish_scan';
truncate table t1;;
Connection default
SET DEBUG_SYNC= 'now WAIT_FOR opened';
set global innodb_purge_run_now=1;
SET DEBUG_SYNC= 'now SIGNAL finish_scan';
connection con1
connection default
drop table t1;
Pattern "InnoDB: Record with space id \d+ belongs to table which is being truncated therefore skipping this undo record." found
# restart server
# restart:
#
# Bug #23070734 CONCURRENT TRUNCATE TABLES CAUSE STALLS
#
create table t1 (f1 int ,f2 int,key(f2)) engine=innodb;
begin;
insert into t1 values (10,45),(20,78),(30,88),(40,23),(50,78),(60,11),(70,56),(80,90);
delete from t1;
connect con2,localhost,root,,;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
SET @saved_frequency = @@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;
commit;
connect con1,localhost,root,,;
SET DEBUG_SYNC= 'buffer_pool_scan SIGNAL started WAIT_FOR finish_scan';
truncate table t1;
connection con2;
SET DEBUG_SYNC= 'now WAIT_FOR started';
COMMIT;
disconnect con2;
connection default;
InnoDB 0 transactions not purged
SET GLOBAL innodb_purge_rseg_truncate_frequency = @saved_frequency;
SET DEBUG_SYNC = 'now SIGNAL finish_scan';
SET DEBUG_SYNC = 'RESET';
connection con1;
disconnect con1;
connection default;
drop table t1;
......@@ -2,6 +2,8 @@
--source include/have_debug.inc
--source include/have_debug_sync.inc
--source include/count_sessions.inc
--echo #
--echo # Bug #23070734 CONCURRENT TRUNCATE TABLES CAUSE STALLS
--echo #
......@@ -30,19 +32,13 @@ insert into t3 select * from t3;
insert into t3 select * from t3;
SET session lock_wait_timeout = 1;
show variables like 'lock_wait_timeout%';
connect (con1,localhost,root,,);
connect (con2,localhost,root,,);
--echo connection 1
connection con1;
SET DEBUG_SYNC= 'simulate_buffer_pool_scan SIGNAL opened WAIT_FOR finish_scan';
--send truncate table t1;
SET DEBUG_SYNC= 'buffer_pool_scan SIGNAL started WAIT_FOR finish_scan';
send truncate table t1;
--echo connection default
connection default;
SET DEBUG_SYNC= 'now WAIT_FOR opened';
SET DEBUG_SYNC= 'now WAIT_FOR started';
--echo Check Analyze table. Gives lock time out error.
analyze table t1;
......@@ -64,7 +60,7 @@ insert into t1 values (10,89,99);
alter table t1 add column f4 int;
--echo check if table can be created with the same name
--error ER_LOCK_WAIT_TIMEOUT
--error ER_TABLE_EXISTS_ERROR,ER_LOCK_WAIT_TIMEOUT
create table t1 (bd int) engine=innodb;
--echo check if index can be created on table being truncated
......@@ -99,96 +95,28 @@ alter table t2 add column f4 int;
--echo check if index can be created on the other table
create index idx1 on t2(f3);
--echo Check if we can turn off persistent stats off entire instance
SET GLOBAL innodb_stats_persistent=off;
--echo connection con2
connection con2;
--send set global innodb_adaptive_hash_index=off;
connect (con2,localhost,root,,);
send set global innodb_adaptive_hash_index=off;
--echo connection default
connection default;
SET DEBUG_SYNC= 'now SIGNAL finish_scan';
SET DEBUG_SYNC= 'RESET';
--echo connection con1
connection con1;
reap;
disconnect con1;
--echo connection con2
connection con2;
reap;
disconnect con2;
--echo connection default
connection default;
disconnect con1;
disconnect con2;
SET session lock_wait_timeout=default;
set global innodb_adaptive_hash_index=default;
set global innodb_adaptive_hash_index=on;
drop table t1,t2,t3;
SET @@global.innodb_stats_persistent=default;
--echo Test_2 :- Check if purge thread ignores the undo
--echo log records of the table being truncated.
let $restart_parameters = restart: --innodb_purge_threads=1 --innodb_purge_batch_size=1 --innodb_stats_persistent=OFF
--source include/restart_mysqld.inc;
create table t1 (f1 int ,f2 int,key(f2)) engine=innodb;
insert into t1 values (10,45),(20,78),(30,88),(40,23),(50,78),(60,11),(70,56),(80,90);
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
insert into t1 select * from t1;
select count(*) from t1;
--echo Stop the purge thread
set global innodb_purge_stop_now = 1;
delete from t1;
connect (con1,localhost,root,,);
--echo connection con1
connection con1;
SET DEBUG_SYNC= 'simulate_buffer_pool_scan SIGNAL opened WAIT_FOR finish_scan';
--send truncate table t1;
--echo Connection default
connection default;
SET DEBUG_SYNC= 'now WAIT_FOR opened';
set global innodb_purge_run_now=1;
--sleep 60
SET DEBUG_SYNC= 'now SIGNAL finish_scan';
--echo connection con1
connection con1;
reap;
--echo connection default
connection default;
disconnect con1;
drop table t1;
let SEARCH_FILE = $MYSQLTEST_VARDIR/log/mysqld.1.err;
let SEARCH_PATTERN = InnoDB: Record with space id \d+ belongs to table which is being truncated therefore skipping this undo record.;
--source include/search_pattern.inc
#cleanup
--echo # restart server
let $restart_parameters = restart:;
--source include/restart_mysqld.inc
--source include/wait_until_count_sessions.inc
--innodb-purge-threads=1
--innodb-purge-batch-size=1
--innodb-stats-persistent=OFF
--source include/have_innodb.inc
--source include/have_debug.inc
--source include/have_debug_sync.inc
--source include/count_sessions.inc
--echo #
--echo # Bug #23070734 CONCURRENT TRUNCATE TABLES CAUSE STALLS
--echo #
create table t1 (f1 int ,f2 int,key(f2)) engine=innodb;
begin;
insert into t1 values (10,45),(20,78),(30,88),(40,23),(50,78),(60,11),(70,56),(80,90);
delete from t1;
connect (con2,localhost,root,,);
# Stop the purge thread
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connection default;
# Ensure that the history list length will actually be decremented by purge.
SET @saved_frequency = @@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;
commit;
connect (con1,localhost,root,,);
SET DEBUG_SYNC= 'buffer_pool_scan SIGNAL started WAIT_FOR finish_scan';
send truncate table t1;
connection con2;
SET DEBUG_SYNC= 'now WAIT_FOR started';
# Allow purge to proceed, by discarding our read view.
COMMIT;
disconnect con2;
connection default;
# Wait for everything to be purged.
let $wait_counter= 300;
while ($wait_counter)
{
--replace_regex /.*History list length ([0-9]+).*/\1/
let $remaining= `SHOW ENGINE INNODB STATUS`;
if ($remaining == 'InnoDB 0')
{
let $wait_counter= 0;
}
if ($wait_counter)
{
real_sleep 0.1;
dec $wait_counter;
}
}
echo $remaining transactions not purged;
SET GLOBAL innodb_purge_rseg_truncate_frequency = @saved_frequency;
SET DEBUG_SYNC = 'now SIGNAL finish_scan';
SET DEBUG_SYNC = 'RESET';
connection con1;
reap;
disconnect con1;
connection default;
drop table t1;
--source include/wait_until_count_sessions.inc
......@@ -319,14 +319,9 @@ dict_stats_process_entry_from_recalc_pool()
return;
}
if (fil_space_is_being_truncated(table->space)) {
dict_table_close(table, TRUE, FALSE);
mutex_exit(&dict_sys->mutex);
return;
}
ut_ad(!dict_table_is_temporary(table));
/* Check whether table is corrupted */
if (table->corrupted) {
if (!fil_table_accessible(table)) {
dict_table_close(table, TRUE, FALSE);
mutex_exit(&dict_sys->mutex);
return;
......
......@@ -3055,6 +3055,32 @@ fil_close_tablespace(
return(err);
}
/** Determine whether a table can be accessed in operations that are
not (necessarily) protected by meta-data locks.
(Rollback would generally be protected, but rollback of
FOREIGN KEY CASCADE/SET NULL is not protected by meta-data locks
but only by InnoDB table locks, which may be broken by TRUNCATE TABLE.)
@param[in] table persistent table
checked @return whether the table is accessible */
bool
fil_table_accessible(const dict_table_t* table)
{
if (UNIV_UNLIKELY(table->ibd_file_missing || table->corrupted)) {
return(false);
}
if (fil_space_t* space = fil_space_acquire(table->space)) {
bool accessible = !space->is_stopping();
fil_space_release(space);
ut_ad(accessible || dict_table_is_file_per_table(table));
return(accessible);
} else {
/* The tablespace may momentarily be missing during
TRUNCATE TABLE. */
return(false);
}
}
/** Deletes an IBD tablespace, either general or single-table.
The tablespace must be cached in the memory cache. This will delete the
datafile, fil_space_t & fil_node_t entries from the file_system_t cache.
......@@ -3288,7 +3314,7 @@ fil_reinit_space_header(
/* Lock the search latch in shared mode to prevent user
from disabling AHI during the scan */
btr_search_s_lock_all();
DEBUG_SYNC_C("simulate_buffer_pool_scan");
DEBUG_SYNC_C("buffer_pool_scan");
buf_LRU_flush_or_remove_pages(id, BUF_REMOVE_ALL_NO_WRITE, 0);
btr_search_s_unlock_all();
row_mysql_lock_data_dictionary(trx);
......
......@@ -247,6 +247,8 @@ btr_get_search_table(const dict_index_t* index);
# define btr_search_drop_page_hash_index(block)
# define btr_search_s_lock(index)
# define btr_search_s_unlock(index)
# define btr_search_s_lock_all(index)
# define btr_search_s_unlock_all(index)
# define btr_search_x_lock(index)
# define btr_search_x_unlock(index)
# define btr_search_info_update(index, cursor)
......
......@@ -883,6 +883,17 @@ fil_op_replay_rename(
const char* new_name)
MY_ATTRIBUTE((warn_unused_result));
/** Determine whether a table can be accessed in operations that are
not (necessarily) protected by meta-data locks.
(Rollback would generally be protected, but rollback of
FOREIGN KEY CASCADE/SET NULL is not protected by meta-data locks
but only by InnoDB table locks, which may be broken by TRUNCATE TABLE.)
@param[in] table persistent table
checked @return whether the table is accessible */
bool
fil_table_accessible(const dict_table_t* table)
MY_ATTRIBUTE((warn_unused_result, nonnull));
/** Deletes an IBD tablespace, either general or single-table.
The tablespace must be cached in the memory cache. This will delete the
datafile, fil_space_t & fil_node_t entries from the file_system_t cache.
......
......@@ -853,17 +853,10 @@ row_purge_parse_undo_rec(
/* The table has been dropped: no need to do purge */
goto err_exit;
}
ut_ad(!dict_table_is_temporary(node->table));
if (fil_space_is_being_truncated(node->table->space)) {
ut_ad(!dict_table_is_temporary(node->table));
#if UNIV_DEBUG
ib::info() << "Record with space id "
<< node->table->space
<< " belongs to table which is being truncated"
<< " therefore skipping this undo record.";
#endif
ut_ad(dict_table_is_file_per_table(node->table));
if (!fil_table_accessible(node->table)) {
dict_table_close(node->table, FALSE, FALSE);
node->table = NULL;
goto err_exit;
......@@ -887,16 +880,6 @@ row_purge_parse_undo_rec(
innobase_init_vc_templ(node->table);
}
if (node->table->ibd_file_missing) {
/* We skip purge of missing .ibd files */
dict_table_close(node->table, FALSE, FALSE);
node->table = NULL;
goto err_exit;
}
clust_index = dict_table_get_first_index(node->table);
if (clust_index == NULL
......
......@@ -346,12 +346,20 @@ row_undo_ins_parse_undo_rec(
/* Skip the UNDO if we can't find the table or the .ibd file. */
if (UNIV_UNLIKELY(node->table == NULL)) {
} else if (UNIV_UNLIKELY(node->table->ibd_file_missing)) {
close_table:
dict_table_close(node->table, dict_locked, FALSE);
node->table = NULL;
} else if (fil_space_is_being_truncated(node->table->space)) {
return;
}
if (UNIV_UNLIKELY(!fil_table_accessible(node->table))) {
close_table:
/* Normally, tables should not disappear or become
unaccessible during ROLLBACK, because they should be
protected by InnoDB table locks. TRUNCATE TABLE
or table corruption could be valid exceptions.
FIXME: When running out of temporary tablespace, it
would probably be better to just drop all temporary
tables (and temporary undo log records) of the current
connection, instead of doing this rollback. */
dict_table_close(node->table, dict_locked, FALSE);
node->table = NULL;
} else {
......@@ -362,6 +370,9 @@ row_undo_ins_parse_undo_rec(
ptr, clust_index, &node->ref, node->heap);
if (!row_undo_search_clust_to_pcur(node)) {
/* An error probably occurred during
an insert into the clustered index,
after we wrote the undo log record. */
goto close_table;
}
if (node->table->n_v_cols) {
......
......@@ -1139,8 +1139,17 @@ row_undo_mod_parse_undo_rec(
return;
}
if (node->table->ibd_file_missing ||
fil_space_is_being_truncated(node->table->space) ) {
if (UNIV_UNLIKELY(!fil_table_accessible(node->table))) {
close_table:
/* Normally, tables should not disappear or become
unaccessible during ROLLBACK, because they should be
protected by InnoDB table locks. TRUNCATE TABLE
or table corruption could be valid exceptions.
FIXME: When running out of temporary tablespace, it
would probably be better to just drop all temporary
tables (and temporary undo log records) of the current
connection, instead of doing this rollback. */
dict_table_close(node->table, dict_locked, FALSE);
node->table = NULL;
return;
......@@ -1160,15 +1169,21 @@ row_undo_mod_parse_undo_rec(
node->new_trx_id = trx_id;
node->cmpl_info = cmpl_info;
if (!row_undo_search_clust_to_pcur(node)) {
dict_table_close(node->table, dict_locked, FALSE);
node->table = NULL;
if (UNIV_UNLIKELY(!row_undo_search_clust_to_pcur(node))) {
/* This should never occur. As long as this
rolling-back transaction exists, the PRIMARY KEY value
pointed to by the undo log record must exist.
btr_cur_upd_lock_and_undo() only writes the undo log
record after successfully acquiring an exclusive lock
on the the clustered index record. That lock will not
be released before the transaction is committed or
fully rolled back. */
ut_ad(0);
goto close_table;
}
/* Extract indexed virtual columns from undo log */
if (node->table && node->table->n_v_cols) {
if (node->table->n_v_cols) {
row_upd_replace_vcol(node->row, node->table,
node->update, false, node->undo_row,
(node->cmpl_info & UPD_NODE_NO_ORD_CHANGE)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment