Commit 3209bc66 authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-26636: InnoDB defragmentation statistics cause races on TEMPORARY TABLE

btr_defragment_save_defrag_stats_if_needed(): Do not save
defragmentation statistics for temporary tables.
They are exempt of defragmentation anyway
(ha_innobase::optimize() never invokes defragmentation for them),
and the user-visible names are not available inside InnoDB.

Furthermore, InnoDB assumes that temporary tables are never accessed
by other threads than the one that handles the session with which
the temporary table is associated with.

Furthermore, we simplify the test innodb.innodb_defrag_stats
and include a test case that demonstrates that defragmentation
statistics are no longer being saved for temporary tables.
parent 496d3dde
DROP TABLE if exists t1; SET GLOBAL innodb_defragment_stats_accuracy = 20;
select @@global.innodb_stats_persistent; DELETE FROM mysql.innodb_index_stats;
@@global.innodb_stats_persistent
0
set global innodb_defragment_stats_accuracy = 20;
# Create table. # Create table.
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b VARCHAR(256), KEY SECOND(a, b)) ENGINE=INNODB; CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b VARCHAR(256), KEY SECOND(a, b)) ENGINE=INNODB;
# Populate data INSERT INTO t1 SELECT seq, REPEAT('A', 256) FROM seq_1_to_1024;
INSERT INTO t1 VALUES(1, REPEAT('A', 256));
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
# Not enough page splits to trigger persistent stats write yet. # Not enough page splits to trigger persistent stats write yet.
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split');
count(stat_value) = 0 count(stat_value) = 0
...@@ -27,7 +13,7 @@ count(stat_value) = 0 ...@@ -27,7 +13,7 @@ count(stat_value) = 0
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_leaf_pages_defrag'); select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_leaf_pages_defrag');
count(stat_value) = 0 count(stat_value) = 0
1 1
INSERT INTO t1 (b) SELECT b from t1; INSERT INTO t1 SELECT seq, REPEAT('A', 256) FROM seq_1025_to_2048;
# Persistent stats recorded. # Persistent stats recorded.
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split');
count(stat_value) > 0 count(stat_value) > 0
...@@ -39,6 +25,7 @@ select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like ...@@ -39,6 +25,7 @@ select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like
count(stat_value) > 0 count(stat_value) > 0
1 1
# Delete some rows. # Delete some rows.
BEGIN;
delete from t1 where a between 100 * 20 and 100 * 20 + 30; delete from t1 where a between 100 * 20 and 100 * 20 + 30;
delete from t1 where a between 100 * 19 and 100 * 19 + 30; delete from t1 where a between 100 * 19 and 100 * 19 + 30;
delete from t1 where a between 100 * 18 and 100 * 18 + 30; delete from t1 where a between 100 * 18 and 100 * 18 + 30;
...@@ -59,8 +46,7 @@ delete from t1 where a between 100 * 4 and 100 * 4 + 30; ...@@ -59,8 +46,7 @@ delete from t1 where a between 100 * 4 and 100 * 4 + 30;
delete from t1 where a between 100 * 3 and 100 * 3 + 30; delete from t1 where a between 100 * 3 and 100 * 3 + 30;
delete from t1 where a between 100 * 2 and 100 * 2 + 30; delete from t1 where a between 100 * 2 and 100 * 2 + 30;
delete from t1 where a between 100 * 1 and 100 * 1 + 30; delete from t1 where a between 100 * 1 and 100 * 1 + 30;
# Server Restarted COMMIT;
# Confirm persistent stats still there after restart.
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split');
count(stat_value) > 0 count(stat_value) > 0
1 1
...@@ -73,9 +59,6 @@ count(stat_value) > 0 ...@@ -73,9 +59,6 @@ count(stat_value) > 0
optimize table t1; optimize table t1;
Table Op Msg_type Msg_text Table Op Msg_type Msg_text
test.t1 optimize status OK test.t1 optimize status OK
select sleep(2);
sleep(2)
0
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split');
count(stat_value) > 0 count(stat_value) > 0
1 1
...@@ -108,9 +91,6 @@ count(stat_value) > 0 ...@@ -108,9 +91,6 @@ count(stat_value) > 0
1 1
# Table rename should cause stats rename. # Table rename should cause stats rename.
rename table t1 to t2; rename table t1 to t2;
select sleep(1);
sleep(1)
0
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split');
count(stat_value) = 0 count(stat_value) = 0
1 1
...@@ -129,47 +109,36 @@ count(stat_value) > 0 ...@@ -129,47 +109,36 @@ count(stat_value) > 0
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_leaf_pages_defrag'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_leaf_pages_defrag');
count(stat_value) > 0 count(stat_value) > 0
1 1
# Drop index should cause stats drop. # Drop index should cause stats drop, but will not.
drop index SECOND on t2; drop index SECOND on t2;
select sleep(3); SELECT stat_name, stat_value>0 FROM mysql.innodb_index_stats
sleep(3) WHERE table_name like '%t2%' AND index_name='SECOND';
0 stat_name stat_value>0
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and index_name = 'SECOND' and stat_name in ('n_page_split'); n_leaf_pages_defrag 1
count(stat_value) > 0 n_leaf_pages_reserved 1
1 n_page_split 1
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and index_name = 'SECOND' and stat_name in ('n_pages_freed'); n_pages_freed 1
count(stat_value) > 0 #
1 # MDEV-26636: Statistics must not be written for temporary tables
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and index_name = 'SECOND' and stat_name in ('n_leaf_pages_defrag'); #
count(stat_value) > 0 SET GLOBAL innodb_defragment_stats_accuracy = 1;
1 CREATE TEMPORARY TABLE t (a INT PRIMARY KEY, c CHAR(255) NOT NULL)
Server Restarted ENGINE=InnoDB;
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); INSERT INTO t SELECT seq, '' FROM seq_1_to_100;
count(stat_value) = 0 SELECT * FROM mysql.innodb_index_stats where table_name like '%t1%';
1 database_name table_name index_name last_update stat_name stat_value sample_size stat_description
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_pages_freed'); SELECT table_name, index_name, stat_name, stat_value>0
count(stat_value) = 0 FROM mysql.innodb_index_stats;
1 table_name index_name stat_name stat_value>0
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_leaf_pages_defrag'); t2 PRIMARY n_leaf_pages_defrag 1
count(stat_value) = 0 t2 PRIMARY n_leaf_pages_reserved 1
1 t2 PRIMARY n_page_split 1
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_page_split'); t2 PRIMARY n_pages_freed 1
count(stat_value) > 0 t2 SECOND n_leaf_pages_defrag 1
1 t2 SECOND n_leaf_pages_reserved 1
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_pages_freed'); t2 SECOND n_page_split 1
count(stat_value) > 0 t2 SECOND n_pages_freed 1
1
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_leaf_pages_defrag');
count(stat_value) > 0
1
# Clean up # Clean up
DROP TABLE t2; DROP TABLE t2;
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_page_split'); SELECT * FROM mysql.innodb_index_stats;
count(stat_value) = 0 database_name table_name index_name last_update stat_name stat_value sample_size stat_description
1
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_pages_freed');
count(stat_value) = 0
1
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_leaf_pages_defrag');
count(stat_value) = 0
1
--source include/have_innodb.inc --source include/have_innodb.inc
--source include/big_test.inc
--source include/not_valgrind.inc --source include/not_valgrind.inc
--source include/not_embedded.inc --source include/not_embedded.inc
--source include/have_sequence.inc
--disable_warnings SET GLOBAL innodb_defragment_stats_accuracy = 20;
DROP TABLE if exists t1;
--enable_warnings
--disable_query_log DELETE FROM mysql.innodb_index_stats;
let $innodb_defragment_stats_accuracy_orig=`select @@innodb_defragment_stats_accuracy`;
--enable_query_log
select @@global.innodb_stats_persistent;
set global innodb_defragment_stats_accuracy = 20;
--echo # Create table. --echo # Create table.
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b VARCHAR(256), KEY SECOND(a, b)) ENGINE=INNODB; CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b VARCHAR(256), KEY SECOND(a, b)) ENGINE=INNODB;
--echo # Populate data INSERT INTO t1 SELECT seq, REPEAT('A', 256) FROM seq_1_to_1024;
INSERT INTO t1 VALUES(1, REPEAT('A', 256));
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
INSERT INTO t1 (b) SELECT b from t1;
--echo # Not enough page splits to trigger persistent stats write yet. --echo # Not enough page splits to trigger persistent stats write yet.
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split');
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_pages_freed'); select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_pages_freed');
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_leaf_pages_defrag'); select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_leaf_pages_defrag');
INSERT INTO t1 (b) SELECT b from t1; INSERT INTO t1 SELECT seq, REPEAT('A', 256) FROM seq_1025_to_2048;
--echo # Persistent stats recorded. --echo # Persistent stats recorded.
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split');
...@@ -43,6 +25,7 @@ select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like ...@@ -43,6 +25,7 @@ select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_leaf_pages_defrag'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_leaf_pages_defrag');
--echo # Delete some rows. --echo # Delete some rows.
BEGIN;
let $num_delete = 20; let $num_delete = 20;
while ($num_delete) while ($num_delete)
{ {
...@@ -50,17 +33,13 @@ while ($num_delete) ...@@ -50,17 +33,13 @@ while ($num_delete)
eval delete from t1 where a between $j and $j + 30; eval delete from t1 where a between $j and $j + 30;
dec $num_delete; dec $num_delete;
} }
COMMIT;
--source include/restart_mysqld.inc
--echo # Server Restarted
--echo # Confirm persistent stats still there after restart.
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split');
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_pages_freed'); select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_pages_freed');
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_leaf_pages_defrag'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_leaf_pages_defrag');
optimize table t1; optimize table t1;
select sleep(2);
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split');
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_pages_freed'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_pages_freed');
...@@ -84,7 +63,6 @@ select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like ...@@ -84,7 +63,6 @@ select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like
--echo # Table rename should cause stats rename. --echo # Table rename should cause stats rename.
rename table t1 to t2; rename table t1 to t2;
select sleep(1);
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split');
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_pages_freed'); select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_pages_freed');
...@@ -94,32 +72,30 @@ select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like ...@@ -94,32 +72,30 @@ select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_pages_freed'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_pages_freed');
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_leaf_pages_defrag'); select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_leaf_pages_defrag');
--echo # Drop index should cause stats drop. --echo # Drop index should cause stats drop, but will not.
drop index SECOND on t2; drop index SECOND on t2;
select sleep(3);
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and index_name = 'SECOND' and stat_name in ('n_page_split'); --sorted_result
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and index_name = 'SECOND' and stat_name in ('n_pages_freed'); SELECT stat_name, stat_value>0 FROM mysql.innodb_index_stats
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and index_name = 'SECOND' and stat_name in ('n_leaf_pages_defrag'); WHERE table_name like '%t2%' AND index_name='SECOND';
--echo #
--echo # MDEV-26636: Statistics must not be written for temporary tables
--echo #
SET GLOBAL innodb_defragment_stats_accuracy = 1;
CREATE TEMPORARY TABLE t (a INT PRIMARY KEY, c CHAR(255) NOT NULL)
ENGINE=InnoDB;
INSERT INTO t SELECT seq, '' FROM seq_1_to_100;
--source include/restart_mysqld.inc --source include/restart_mysqld.inc
--echo Server Restarted
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_page_split'); SELECT * FROM mysql.innodb_index_stats where table_name like '%t1%';
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_pages_freed');
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t1%' and stat_name in ('n_leaf_pages_defrag');
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_page_split'); --sorted_result
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_pages_freed'); SELECT table_name, index_name, stat_name, stat_value>0
select count(stat_value) > 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_leaf_pages_defrag'); FROM mysql.innodb_index_stats;
--echo # Clean up --echo # Clean up
DROP TABLE t2; DROP TABLE t2;
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_page_split'); SELECT * FROM mysql.innodb_index_stats;
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_pages_freed');
select count(stat_value) = 0 from mysql.innodb_index_stats where table_name like '%t2%' and stat_name in ('n_leaf_pages_defrag');
--disable_query_log
EVAL SET GLOBAL innodb_defragment_stats_accuracy = $innodb_defragment_stats_accuracy_orig;
--enable_query_log
/***************************************************************************** /*****************************************************************************
Copyright (C) 2012, 2014 Facebook, Inc. All Rights Reserved. Copyright (C) 2012, 2014 Facebook, Inc. All Rights Reserved.
Copyright (C) 2014, 2019, MariaDB Corporation. Copyright (C) 2014, 2021, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software the terms of the GNU General Public License as published by the Free Software
...@@ -312,6 +312,7 @@ btr_defragment_save_defrag_stats_if_needed( ...@@ -312,6 +312,7 @@ btr_defragment_save_defrag_stats_if_needed(
{ {
if (srv_defragment_stats_accuracy != 0 // stats tracking disabled if (srv_defragment_stats_accuracy != 0 // stats tracking disabled
&& dict_index_get_space(index) != 0 // do not track system tables && dict_index_get_space(index) != 0 // do not track system tables
&& !index->table->is_temporary()
&& index->stat_defrag_modified_counter && index->stat_defrag_modified_counter
>= srv_defragment_stats_accuracy) { >= srv_defragment_stats_accuracy) {
dict_stats_defrag_pool_add(index); dict_stats_defrag_pool_add(index);
......
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