From e3b36b8f1b5afeeca7868212dd7a4bf4d1a1dc83 Mon Sep 17 00:00:00 2001 From: Monty Date: Fri, 18 Aug 2023 18:35:02 +0300 Subject: [PATCH] MDEV-31957 Concurrent ALTER and ANALYZE collecting statistics can result in stale statistical data Example of what causes the problem: T1: ANALYZE TABLE starts to collect statistics T2: ALTER TABLE starts by deleting statistics for all changed fields, then creates a temp table and copies data to it. T1: ANALYZE ends and writes to the statistics tables. T2: ALTER TABLE renames temp table in place of the old table. Now the statistics from analyze matches the old deleted tables. Fixed by waiting to delete old statistics until ALTER TABLE is the only one using the old table and ensure that rename of columns can handle swapping of column names. rename_columns_in_stat_table() (former rename_column_in_stat_tables()) now takes a list of columns to rename. It uses the following algorithm to update column_stats to be able to handle circular renames - While there are columns to be renamed and it is the first loop or last rename loop did change something. - Loop over all columns to be renamed - Change column name in column_stat - If fail because of duplicate key - If this is first change attempt for this column - Change column name to a temporary column name - If there was a conflicting row, replace it with the current row. else - Remove entry from column list - Loop over all remaining columns in the list - Remove the conflicting row - Change column from temporary name to final name in column_stat Other things: - Don't flush tables for every operation. Only flush when all updates are done. - Rename of columns was not handled in case of ALGORITHM=copy (old bug). - Fixed that we do not collect statistics for hidden hash columns used by UNIQUE constraint on long values. - Fixed that we do not collect statistics for blob columns referred by generated virtual columns. This was achieved by storing the fields for which we want to have statistics in table->has_value_set instead of in table->read_set. - Rename of indexes was not handled for persistent statistics. - This is now handled similar as rename of columns. Renamed columns are now stored in 'rename_stat_indexes' and handled in Alter_info::delete_statistics() together with drooped indexes. - ALTER TABLE .. ADD INDEX may instead of creating a new index rename an existing generated foreign key index. This was not reflected in the index_stats table because this was handled in mysql_prepare_create_table instead instead of in the mysql_alter() code. Fixed by adding a call in mysql_prepare_create_table() to drop the changed index. I also had to change the code that 'marked the index' to be ignored with code that would not destroy the original index name. Reviewer: Sergei Petrunia --- mysql-test/main/analyze.result | 285 ++++++++++++ mysql-test/main/analyze.test | 143 ++++++ mysql-test/main/statistics.result | 19 +- mysql-test/main/statistics.test | 9 +- sql/sql_admin.cc | 53 ++- sql/sql_alter.cc | 75 ++++ sql/sql_alter.h | 82 ++++ sql/sql_class.h | 3 +- sql/sql_statistics.cc | 703 +++++++++++++++++++++++------- sql/sql_statistics.h | 17 +- sql/sql_table.cc | 120 +++-- sql/structs.h | 10 +- 12 files changed, 1313 insertions(+), 206 deletions(-) diff --git a/mysql-test/main/analyze.result b/mysql-test/main/analyze.result index 17325863acd..8d345d98b05 100644 --- a/mysql-test/main/analyze.result +++ b/mysql-test/main/analyze.result @@ -97,5 +97,290 @@ set use_stat_tables=default; set histogram_type=default; set histogram_size=default; # +# MDEV-31957 Concurrent ALTER and ANALYZE collecting statistics can +# result in stale statistical data +# +CREATE TABLE t1 (a INT, b VARCHAR(128)); +INSERT INTO t1 SELECT seq, CONCAT('s',seq) FROM seq_1_to_100; +connect con1,localhost,root,,; +ALTER TABLE t1 MODIFY b BLOB; +connection default; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +connection con1; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +connection default; +disconnect con1; +select db_name,table_name,column_name from mysql.column_stats; +db_name table_name column_name +test t1 a +drop table t1; +# +# Testing swapping columns +# +create or replace table t1 (a int primary key, b varchar(100), c varchar(100), d varchar(100)) engine=innodb; +insert into t1 select seq, repeat('b',seq),repeat('c',mod(seq,5)), repeat('d',mod(seq,10)) from seq_1_to_100; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 b 50.5000 +test t1 c 2.0000 +test t1 d 4.5000 +alter table t1 change b c varchar(200), change c b varchar(200); +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 b 2.0000 +test t1 c 50.5000 +test t1 d 4.5000 +alter table t1 change b c varchar(200), change c d varchar(200), change d b varchar(200) ; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 b 4.5000 +test t1 c 2.0000 +test t1 d 50.5000 +alter table t1 change b c varchar(200), change c d varchar(200), change d e varchar(200) ; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 c 4.5000 +test t1 d 2.0000 +test t1 e 50.5000 +alter table t1 change e d varchar(200), drop column d; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 c 4.5000 +test t1 d 50.5000 +# Test having non existing column in column_stats +insert into mysql.column_stats (db_name,table_name,column_name) values ("test","t1","b"); +alter table t1 change c d varchar(200), change d b varchar(200); +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 b 50.5000 +test t1 d 4.5000 +# Test having a conflicting temporary name +insert into mysql.column_stats (db_name,table_name,column_name) values ("test","t1",concat("#sql_tmp_name#1",char(0))); +alter table t1 change d b varchar(200), change b d varchar(200); +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 b 4.5000 +test t1 d 50.5000 +drop table t1; +truncate table mysql.column_stats; +create or replace table t1 (a int primary key, b varchar(100), c varchar(100), d varchar(100)) engine=myisam; +insert into t1 select seq, repeat('b',seq),repeat('c',mod(seq,5)), repeat('d',mod(seq,10)) from seq_1_to_100; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 b 50.5000 +test t1 c 2.0000 +test t1 d 4.5000 +alter table t1 change b c varchar(200), change c b varchar(200); +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 d 4.5000 +analyze table t1 persistent for columns(b,c) indexes all; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status Table is already up to date +alter table t1 change b c varchar(200), change c d varchar(200), change d b varchar(200) ; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 b 50.5000 +test t1 c 2.0000 +analyze table t1 persistent for columns(d) indexes all; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status Table is already up to date +alter table t1 change b c varchar(200), change c d varchar(200), change d e varchar(200) ; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 c 50.5000 +test t1 d 2.0000 +test t1 e 50.5000 +alter table t1 change e d varchar(200), drop column d; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +db_name table_name column_name avg_length +test t1 a 4.0000 +test t1 c 50.5000 +test t1 d 50.5000 +drop table t1; +truncate table mysql.column_stats; +create table t1 (a int, b blob, unique(b)) engine= innodb; +analyze table t1 persistent for all; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze Warning Engine-independent statistics are not collected for column 'b' +test.t1 analyze status OK +select column_name from mysql.column_stats where table_name = 't1'; +column_name +a +drop table t1; +create table t1 (a int, b blob, c int generated always as (length(b)) virtual) engine= innodb; +analyze table t1 persistent for all; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze Warning Engine-independent statistics are not collected for column 'b' +test.t1 analyze status OK +select column_name from mysql.column_stats where table_name = 't1'; +column_name +a +c +drop table t1; +CREATE or replace TABLE t1 (a INT, b CHAR(8)); +ANALYZE TABLE t1 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status Table is already up to date +ALTER TABLE t1 CHANGE b c INT, ORDER BY b; +SELECT db_name, table_name, column_name FROM mysql.column_stats where table_name = 't1'; +db_name table_name column_name +test t1 a +test t1 c +drop table t1; +CREATE or replace TABLE t1 (a INT, b CHAR(8)); +ANALYZE TABLE t1 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status Table is already up to date +ALTER TABLE t1 RENAME COLUMN b to c, ALGORITHM=COPY; +SELECT db_name, table_name, column_name FROM mysql.column_stats where table_name = 't1'; +db_name table_name column_name +test t1 a +test t1 c +drop table t1; +# +# Testing swapping indexes +# +create or replace table t1 (a int primary key, b varchar(100), c varchar(100), d varchar(100), index (b), index(c), index(d,b)) engine=innodb; +insert into t1 select seq, repeat('b',seq),repeat('c',mod(seq,5)), repeat('d',mod(seq,10)) from seq_1_to_100; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +select * from mysql.index_stats order by index_name, prefix_arity; +db_name table_name index_name prefix_arity avg_frequency +test t1 PRIMARY 1 1.0000 +test t1 b 1 1.0000 +test t1 b 2 1.0000 +test t1 c 1 20.0000 +test t1 c 2 1.0000 +test t1 d 1 10.0000 +test t1 d 2 1.0000 +test t1 d 3 1.0000 +alter table t1 rename index b to c, rename index c to d, rename index d to b; +select * from mysql.index_stats order by index_name; +db_name table_name index_name prefix_arity avg_frequency +test t1 PRIMARY 1 1.0000 +test t1 b 1 10.0000 +test t1 b 2 1.0000 +test t1 b 3 1.0000 +test t1 c 1 1.0000 +test t1 c 2 1.0000 +test t1 d 1 20.0000 +test t1 d 2 1.0000 +alter table t1 rename index b to c, rename index c to d, rename index d to e; +select * from mysql.index_stats order by index_name, prefix_arity; +db_name table_name index_name prefix_arity avg_frequency +test t1 PRIMARY 1 1.0000 +test t1 c 1 10.0000 +test t1 c 2 1.0000 +test t1 c 3 1.0000 +test t1 d 1 1.0000 +test t1 d 2 1.0000 +test t1 e 1 20.0000 +test t1 e 2 1.0000 +alter table t1 rename index e to b; +alter table t1 change b c varchar(200), change c d varchar(200), change d e varchar(200) ; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `c` varchar(200) DEFAULT NULL, + `d` varchar(200) DEFAULT NULL, + `e` varchar(200) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `d` (`c`), + KEY `b` (`d`), + KEY `c` (`e`,`c`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +select * from mysql.index_stats order by index_name, prefix_arity; +db_name table_name index_name prefix_arity avg_frequency +test t1 PRIMARY 1 1.0000 +test t1 b 1 20.0000 +test t1 b 2 1.0000 +test t1 c 1 10.0000 +test t1 c 2 1.0000 +test t1 c 3 1.0000 +test t1 d 1 1.0000 +test t1 d 2 1.0000 +# Test having a conflicting temporary name +insert into mysql.index_stats (db_name,table_name,index_name,prefix_arity) values ("test","t1",concat("#sql_tmp_name#1",char(0)),1); +alter table t1 rename index c to d, rename index d to c; +select * from mysql.index_stats order by index_name, prefix_arity; +db_name table_name index_name prefix_arity avg_frequency +test t1 PRIMARY 1 1.0000 +test t1 b 1 20.0000 +test t1 b 2 1.0000 +test t1 c 1 1.0000 +test t1 c 2 1.0000 +test t1 d 1 10.0000 +test t1 d 2 1.0000 +test t1 d 3 1.0000 +drop table t1; +select * from mysql.index_stats order by index_name, prefix_arity; +db_name table_name index_name prefix_arity avg_frequency +# +# Test of adding key that replaces foreign key +# +CREATE TABLE t1 (aaaa INT, b INT, KEY(b), FOREIGN KEY(aaaa) REFERENCES t1(b)) ENGINE=InnoDB; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +SELECT index_name FROM mysql.index_stats WHERE table_name = 't1' order by index_name; +index_name +aaaa +b +ALTER TABLE t1 ADD KEY idx(aaaa); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `aaaa` int(11) DEFAULT NULL, + `b` int(11) DEFAULT NULL, + KEY `b` (`b`), + KEY `idx` (`aaaa`), + CONSTRAINT `t1_ibfk_1` FOREIGN KEY (`aaaa`) REFERENCES `t1` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +SELECT index_name FROM mysql.index_stats WHERE table_name = 't1' order by index_name; +index_name +b +truncate table mysql.index_stats; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +SELECT index_name FROM mysql.index_stats WHERE table_name = 't1' order by index_name; +index_name +b +idx +ALTER TABLE t1 DROP KEY idx; +ERROR HY000: Cannot drop index 'idx': needed in a foreign key constraint +DROP TABLE t1; +# # End of 10.6 tests # diff --git a/mysql-test/main/analyze.test b/mysql-test/main/analyze.test index 3c7179fbf19..d89e05b8543 100644 --- a/mysql-test/main/analyze.test +++ b/mysql-test/main/analyze.test @@ -1,4 +1,5 @@ --source include/have_sequence.inc +--source include/have_innodb.inc # # Bug #10901 Analyze Table on new table destroys table @@ -111,6 +112,148 @@ set use_stat_tables=default; set histogram_type=default; set histogram_size=default; +--echo # +--echo # MDEV-31957 Concurrent ALTER and ANALYZE collecting statistics can +--echo # result in stale statistical data +--echo # + +CREATE TABLE t1 (a INT, b VARCHAR(128)); +INSERT INTO t1 SELECT seq, CONCAT('s',seq) FROM seq_1_to_100; + +# We have to disable query log as the ANALYZE TABLE can be run in different +# order. The important thing is what is finally in column_stats +--disable_result_log +--connect (con1,localhost,root,,) +--send ALTER TABLE t1 MODIFY b BLOB + +--connection default +ANALYZE TABLE t1 PERSISTENT FOR ALL; + +--connection con1 +--reap +ANALYZE TABLE t1 PERSISTENT FOR ALL; +--connection default +--disconnect con1 +--enable_result_log +select db_name,table_name,column_name from mysql.column_stats; +drop table t1; + +--echo # +--echo # Testing swapping columns +--echo # + +create or replace table t1 (a int primary key, b varchar(100), c varchar(100), d varchar(100)) engine=innodb; +insert into t1 select seq, repeat('b',seq),repeat('c',mod(seq,5)), repeat('d',mod(seq,10)) from seq_1_to_100; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +alter table t1 change b c varchar(200), change c b varchar(200); +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +alter table t1 change b c varchar(200), change c d varchar(200), change d b varchar(200) ; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +alter table t1 change b c varchar(200), change c d varchar(200), change d e varchar(200) ; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +alter table t1 change e d varchar(200), drop column d; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; + +--echo # Test having non existing column in column_stats + +insert into mysql.column_stats (db_name,table_name,column_name) values ("test","t1","b"); +alter table t1 change c d varchar(200), change d b varchar(200); +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; + +--echo # Test having a conflicting temporary name +insert into mysql.column_stats (db_name,table_name,column_name) values ("test","t1",concat("#sql_tmp_name#1",char(0))); +alter table t1 change d b varchar(200), change b d varchar(200); +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; + +drop table t1; +truncate table mysql.column_stats; + +create or replace table t1 (a int primary key, b varchar(100), c varchar(100), d varchar(100)) engine=myisam; +insert into t1 select seq, repeat('b',seq),repeat('c',mod(seq,5)), repeat('d',mod(seq,10)) from seq_1_to_100; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +alter table t1 change b c varchar(200), change c b varchar(200); +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +analyze table t1 persistent for columns(b,c) indexes all; +alter table t1 change b c varchar(200), change c d varchar(200), change d b varchar(200) ; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +analyze table t1 persistent for columns(d) indexes all; +alter table t1 change b c varchar(200), change c d varchar(200), change d e varchar(200) ; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +alter table t1 change e d varchar(200), drop column d; +select db_name,table_name,column_name,avg_length from mysql.column_stats order by column_name; +drop table t1; +truncate table mysql.column_stats; + +create table t1 (a int, b blob, unique(b)) engine= innodb; +analyze table t1 persistent for all; +select column_name from mysql.column_stats where table_name = 't1'; +drop table t1; + +create table t1 (a int, b blob, c int generated always as (length(b)) virtual) engine= innodb; +analyze table t1 persistent for all; +select column_name from mysql.column_stats where table_name = 't1'; +drop table t1; + +CREATE or replace TABLE t1 (a INT, b CHAR(8)); +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ALTER TABLE t1 CHANGE b c INT, ORDER BY b; +SELECT db_name, table_name, column_name FROM mysql.column_stats where table_name = 't1'; +drop table t1; + +CREATE or replace TABLE t1 (a INT, b CHAR(8)); +ANALYZE TABLE t1 PERSISTENT FOR ALL; +ALTER TABLE t1 RENAME COLUMN b to c, ALGORITHM=COPY; +SELECT db_name, table_name, column_name FROM mysql.column_stats where table_name = 't1'; +drop table t1; + +--echo # +--echo # Testing swapping indexes +--echo # + +create or replace table t1 (a int primary key, b varchar(100), c varchar(100), d varchar(100), index (b), index(c), index(d,b)) engine=innodb; +insert into t1 select seq, repeat('b',seq),repeat('c',mod(seq,5)), repeat('d',mod(seq,10)) from seq_1_to_100; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +select * from mysql.index_stats order by index_name, prefix_arity; +alter table t1 rename index b to c, rename index c to d, rename index d to b; +select * from mysql.index_stats order by index_name; + +alter table t1 rename index b to c, rename index c to d, rename index d to e; +select * from mysql.index_stats order by index_name, prefix_arity; +alter table t1 rename index e to b; +alter table t1 change b c varchar(200), change c d varchar(200), change d e varchar(200) ; +show create table t1; +select * from mysql.index_stats order by index_name, prefix_arity; + +--echo # Test having a conflicting temporary name +insert into mysql.index_stats (db_name,table_name,index_name,prefix_arity) values ("test","t1",concat("#sql_tmp_name#1",char(0)),1); +alter table t1 rename index c to d, rename index d to c; +select * from mysql.index_stats order by index_name, prefix_arity; +drop table t1; +select * from mysql.index_stats order by index_name, prefix_arity; + +--echo # +--echo # Test of adding key that replaces foreign key +--echo # + +CREATE TABLE t1 (aaaa INT, b INT, KEY(b), FOREIGN KEY(aaaa) REFERENCES t1(b)) ENGINE=InnoDB; +ANALYZE TABLE t1 PERSISTENT FOR ALL; + +SELECT index_name FROM mysql.index_stats WHERE table_name = 't1' order by index_name; + +ALTER TABLE t1 ADD KEY idx(aaaa); +SHOW CREATE TABLE t1; + +SELECT index_name FROM mysql.index_stats WHERE table_name = 't1' order by index_name; + +truncate table mysql.index_stats; +ANALYZE TABLE t1 PERSISTENT FOR ALL; +SELECT index_name FROM mysql.index_stats WHERE table_name = 't1' order by index_name; + +--error ER_DROP_INDEX_FK +ALTER TABLE t1 DROP KEY idx; +DROP TABLE t1; --echo # --echo # End of 10.6 tests --echo # diff --git a/mysql-test/main/statistics.result b/mysql-test/main/statistics.result index 8677b992b03..a442a33d14b 100644 --- a/mysql-test/main/statistics.result +++ b/mysql-test/main/statistics.result @@ -552,6 +552,13 @@ test t1 PRIMARY 1 1.0000 test t1 idx2 1 7.0000 test t1 idx2 2 2.3846 test t1 idx3 1 8.5000 +ANALYZE TABLE t1 PERSISTENT FOR COLUMNS(x) INDEXES(); +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +SELECT * FROM mysql.column_stats where column_name="x"; +db_name table_name column_name min_value max_value nulls_ratio avg_length avg_frequency hist_size hist_type histogram +test t1 x vvvvvvvvvvvvv zzzzzzzzzzzzzzzzzz 0.2000 17.1250 6.4000 0 NULL NULL ALTER TABLE t1 CHANGE COLUMN x b varchar(32); SHOW CREATE TABLE t1; Table Create Table @@ -668,8 +675,8 @@ test t1 PRIMARY 1 1.0000 test t1 idx2 1 7.0000 test t1 idx2 2 2.3846 test t1 idx3 1 8.5000 -LOAD DATA INFILE 'MYSQLTEST_VARDIR/tmp/save_column_stats' - INTO TABLE mysql.column_stats +LOAD DATA INFILE 'MYSQLTEST_VARDIR/tmp/save_column_stats' IGNORE +INTO TABLE mysql.column_stats FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n'; LOAD DATA INFILE 'MYSQLTEST_VARDIR/tmp/save_index_stats' INTO TABLE mysql.index_stats @@ -1106,12 +1113,17 @@ test t2 idx4 1 6.2000 test t2 idx4 2 1.7222 test t2 idx4 3 1.1154 test t2 idx4 4 1.0000 +SELECT * FROM mysql.column_stats where column_name="b"; +db_name table_name column_name min_value max_value nulls_ratio avg_length avg_frequency hist_size hist_type histogram +test t2 b vvvvvvvvvvvvv zzzzzzzzzzzzzzzzzz 0.2000 17.1250 6.4000 0 NULL NULL ALTER TABLE t2 CHANGE COLUMN b b varchar(30); SELECT * FROM mysql.index_stats ORDER BY index_name, prefix_arity, table_name; db_name table_name index_name prefix_arity avg_frequency test t2 idx2 1 7.0000 test t2 idx2 2 2.3846 test t2 idx3 1 8.5000 +SELECT * FROM mysql.column_stats where column_name="b"; +db_name table_name column_name min_value max_value nulls_ratio avg_length avg_frequency hist_size hist_type histogram ANALYZE TABLE t2 PERSISTENT FOR COLUMNS ALL INDEXES ALL; Table Op Msg_type Msg_text test.t2 analyze status Engine-independent statistics collected @@ -1147,6 +1159,9 @@ test t2 idx4 1 6.2000 test t2 idx4 2 1.7222 test t2 idx4 3 1.1154 test t2 idx4 4 1.0000 +SELECT * FROM mysql.column_stats where column_name="b"; +db_name table_name column_name min_value max_value nulls_ratio avg_length avg_frequency hist_size hist_type histogram +test t2 b 0 zzzzzzzzzzzzzzzzzz 0.0000 13.9000 6.6667 0 NULL NULL ANALYZE TABLE t2 PERSISTENT FOR COLUMNS ALL INDEXES ALL; Table Op Msg_type Msg_text test.t2 analyze status Engine-independent statistics collected diff --git a/mysql-test/main/statistics.test b/mysql-test/main/statistics.test index e6b300b5db2..e0b92ec2e40 100644 --- a/mysql-test/main/statistics.test +++ b/mysql-test/main/statistics.test @@ -305,6 +305,10 @@ SELECT * FROM mysql.column_stats; --sorted_result SELECT * FROM mysql.index_stats; +# Add 'x' back that was deleted above +ANALYZE TABLE t1 PERSISTENT FOR COLUMNS(x) INDEXES(); +SELECT * FROM mysql.column_stats where column_name="x"; + ALTER TABLE t1 CHANGE COLUMN x b varchar(32); SHOW CREATE TABLE t1; --sorted_result @@ -347,7 +351,7 @@ SELECT * FROM mysql.index_stats; --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR eval -LOAD DATA INFILE '$MYSQLTEST_VARDIR/tmp/save_column_stats' +LOAD DATA INFILE '$MYSQLTEST_VARDIR/tmp/save_column_stats' IGNORE INTO TABLE mysql.column_stats FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n'; --replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR @@ -495,14 +499,17 @@ SELECT * FROM mysql.index_stats ORDER BY index_name, prefix_arity, table_name; ANALYZE TABLE t2 PERSISTENT FOR COLUMNS() INDEXES ALL; SELECT * FROM mysql.index_stats ORDER BY index_name, prefix_arity, table_name; +SELECT * FROM mysql.column_stats where column_name="b"; ALTER TABLE t2 CHANGE COLUMN b b varchar(30); SELECT * FROM mysql.index_stats ORDER BY index_name, prefix_arity, table_name; +SELECT * FROM mysql.column_stats where column_name="b"; ANALYZE TABLE t2 PERSISTENT FOR COLUMNS ALL INDEXES ALL; SELECT * FROM mysql.index_stats ORDER BY index_name, prefix_arity, table_name; ALTER TABLE t2 CHANGE COLUMN b b varchar(32); SELECT * FROM mysql.index_stats ORDER BY index_name, prefix_arity, table_name; +SELECT * FROM mysql.column_stats where column_name="b"; ANALYZE TABLE t2 PERSISTENT FOR COLUMNS ALL INDEXES ALL; SELECT * FROM mysql.index_stats ORDER BY index_name, prefix_arity, table_name; diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index b7a845f04c9..bde633b174c 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -517,8 +517,6 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, List field_list; Protocol *protocol= thd->protocol; LEX *lex= thd->lex; - int result_code; - int compl_result_code; bool need_repair_or_alter= 0; wait_for_commit* suspended_wfc; bool is_table_modified= false; @@ -562,7 +560,9 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, bool collect_eis= FALSE; bool open_for_modify= org_open_for_modify; Recreate_info recreate_info; + int compl_result_code, result_code; + compl_result_code= result_code= HA_ADMIN_FAILED; storage_engine_name[0]= 0; // Marker that's not used DBUG_PRINT("admin", ("table: '%s'.'%s'", db, table->table_name.str)); @@ -880,6 +880,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, DBUG_PRINT("admin", ("operator_func returned: %d", result_code)); } + /* Note: compl_result_code can be different from result_code here */ if (compl_result_code == HA_ADMIN_OK && collect_eis) { if (result_code == HA_ERR_TABLE_READONLY) @@ -920,19 +921,35 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, Field **field_ptr= tab->field; if (!lex->column_list) { + /* Fields we have to read from the engine */ bitmap_clear_all(tab->read_set); + /* Fields we want to have statistics for */ + bitmap_clear_all(&tab->has_value_set); + for (uint fields= 0; *field_ptr; field_ptr++, fields++) { + Field *field= *field_ptr; + if (field->flags & LONG_UNIQUE_HASH_FIELD) + { + /* + No point in doing statistic for hash fields that should be + unique + */ + continue; + } /* Note that type() always return MYSQL_TYPE_BLOB for all blob types. Another function needs to be added if we in the future want to distingush between blob types here. */ - enum enum_field_types type= (*field_ptr)->type(); + enum enum_field_types type= field->type(); if (type < MYSQL_TYPE_TINY_BLOB || type > MYSQL_TYPE_BLOB) - tab->field[fields]->register_field_in_read_map(); + { + field->register_field_in_read_map(); + bitmap_set_bit(&tab->has_value_set, field->field_index); + } else push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NO_EIS_FOR_FIELD, @@ -946,9 +963,15 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, LEX_STRING *column_name; List_iterator_fast it(*lex->column_list); + /* Fields we have to read from the engine */ bitmap_clear_all(tab->read_set); + /* Fields we want to have statistics for */ + bitmap_clear_all(&tab->has_value_set); + while ((column_name= it++)) { + Field *field; + enum enum_field_types type; if (tab->s->fieldnames.type_names == 0 || (pos= find_type(&tab->s->fieldnames, column_name->str, column_name->length, 1)) <= 0) @@ -957,10 +980,15 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, break; } pos--; - enum enum_field_types type= tab->field[pos]->type(); - if (type < MYSQL_TYPE_TINY_BLOB || - type > MYSQL_TYPE_BLOB) - tab->field[pos]->register_field_in_read_map(); + field= tab->field[pos]; + type= field->type(); + if (!(field->flags & LONG_UNIQUE_HASH_FIELD) && + (type < MYSQL_TYPE_TINY_BLOB || + type > MYSQL_TYPE_BLOB)) + { + field->register_field_in_read_map(); + bitmap_set_bit(&tab->has_value_set, field->field_index); + } else push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NO_EIS_FOR_FIELD, @@ -991,12 +1019,13 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, } } /* Ensure that number of records are updated */ - table->table->file->info(HA_STATUS_VARIABLE); + tab->file->info(HA_STATUS_VARIABLE); if (!(compl_result_code= - alloc_statistics_for_table(thd, table->table)) && + alloc_statistics_for_table(thd, tab, + &tab->has_value_set)) && !(compl_result_code= - collect_statistics_for_table(thd, table->table))) - compl_result_code= update_statistics_for_table(thd, table->table); + collect_statistics_for_table(thd, tab))) + compl_result_code= update_statistics_for_table(thd, tab); } else compl_result_code= HA_ADMIN_FAILED; diff --git a/sql/sql_alter.cc b/sql/sql_alter.cc index f7367dd95aa..3a9dd8c6fbe 100644 --- a/sql/sql_alter.cc +++ b/sql/sql_alter.cc @@ -18,6 +18,7 @@ #include "sql_parse.h" // check_access #include "sql_table.h" // mysql_alter_table, // mysql_exchange_partition +#include "sql_statistics.h" // delete_statistics_for_column #include "sql_alter.h" #include "wsrep_mysqld.h" @@ -31,6 +32,7 @@ Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root) check_constraint_list(rhs.check_constraint_list, mem_root), flags(rhs.flags), partition_flags(rhs.partition_flags), keys_onoff(rhs.keys_onoff), + original_table(0), partition_names(rhs.partition_names, mem_root), num_parts(rhs.num_parts), requested_algorithm(rhs.requested_algorithm), @@ -297,6 +299,79 @@ uint Alter_info::check_vcol_field(Item_field *item) const } +bool Alter_info::collect_renamed_fields(THD *thd) +{ + List_iterator_fast new_field_it; + Create_field *new_field; + DBUG_ENTER("Alter_info::collect_renamed_fields"); + + new_field_it.init(create_list); + while ((new_field= new_field_it++)) + { + Field *field= new_field->field; + + if (new_field->field && + cmp(&field->field_name, &new_field->field_name)) + { + field->flags|= FIELD_IS_RENAMED; + if (add_stat_rename_field(field, + &new_field->field_name, + thd->mem_root)) + DBUG_RETURN(true); + + } + } + DBUG_RETURN(false); +} + + +/* + Delete duplicate index found during mysql_prepare_create_table() + + Notes: + - In case of temporary generated foreign keys, the key_name may not + be set! These keys are ignored. +*/ + +bool Alter_info::add_stat_drop_index(THD *thd, const LEX_CSTRING *key_name) +{ + if (original_table && key_name->length) // If from alter table + { + KEY *key_info= original_table->key_info; + for (uint i= 0; i < original_table->s->keys; i++, key_info++) + { + if (key_info->name.length && + !lex_string_cmp(system_charset_info, &key_info->name, + key_name)) + return add_stat_drop_index(key_info, false, thd->mem_root); + } + } + return false; +} + + +void Alter_info::apply_statistics_deletes_renames(THD *thd, TABLE *table) +{ + List_iterator it_drop_field(drop_stat_fields); + List_iterator it_rename_field(rename_stat_fields); + List_iterator it_drop_index(drop_stat_indexes); + List_iterator it_rename_index(rename_stat_indexes); + + while (Field *field= it_drop_field++) + delete_statistics_for_column(thd, table, field); + + if (!rename_stat_fields.is_empty()) + (void) rename_columns_in_stat_table(thd, table, &rename_stat_fields); + + while (DROP_INDEX_STAT_PARAMS *key= it_drop_index++) + (void) delete_statistics_for_index(thd, table, key->key, + key->ext_prefixes_only); + + if (!rename_stat_indexes.is_empty()) + (void) rename_indexes_in_stat_table(thd, table, &rename_stat_indexes); +} + + Alter_table_ctx::Alter_table_ctx() : db(null_clex_str), table_name(null_clex_str), alias(null_clex_str), new_db(null_clex_str), new_name(null_clex_str), new_alias(null_clex_str) diff --git a/sql/sql_alter.h b/sql/sql_alter.h index 4605a6d415a..24203716cb2 100644 --- a/sql/sql_alter.h +++ b/sql/sql_alter.h @@ -105,10 +105,88 @@ public: ulong partition_flags; // Enable or disable keys. enum_enable_or_disable keys_onoff; + // Used only in add_stat_drop_index() + TABLE *original_table; // List of partitions. List partition_names; // Number of partitions. uint num_parts; + + /* List of fields that we should delete statistics from */ + List drop_stat_fields; + + struct DROP_INDEX_STAT_PARAMS + { + KEY *key; + bool ext_prefixes_only; + }; + + struct RENAME_COLUMN_STAT_PARAMS + { + Field *field; + LEX_CSTRING *name; + uint duplicate_counter; // For temporary names + }; + struct RENAME_INDEX_STAT_PARAMS + { + const KEY *key; + const LEX_CSTRING *name; + uint duplicate_counter; // For temporary names + uint usage_count; // How many rename entries + }; + + /* List of index that we should delete statistics from */ + List drop_stat_indexes; + + List rename_stat_fields; + + List rename_stat_indexes; + + bool add_stat_drop_index(KEY *key, bool ext_prefixes_only, + MEM_ROOT *mem_root) + { + DROP_INDEX_STAT_PARAMS *param; + if (!(param= (DROP_INDEX_STAT_PARAMS*) + alloc_root(mem_root, sizeof(*param)))) + return true; + param->key= key; + param->ext_prefixes_only= ext_prefixes_only; + return drop_stat_indexes.push_back(param, mem_root); + } + + bool add_stat_drop_index(THD *thd, const LEX_CSTRING *key_name); + + bool add_stat_rename_index(const KEY *key, const LEX_CSTRING *name, + MEM_ROOT *mem_root) + { + RENAME_INDEX_STAT_PARAMS *param; + if (!(param= (RENAME_INDEX_STAT_PARAMS*) + alloc_root(mem_root, sizeof(*param)))) + return true; + param->key= key; + param->name= name; + param->usage_count= 0; + return rename_stat_indexes.push_back(param, mem_root); + } + + bool add_stat_rename_field(Field *field, LEX_CSTRING *name, + MEM_ROOT *mem_root) + { + RENAME_COLUMN_STAT_PARAMS *param; + if (!(param= (RENAME_COLUMN_STAT_PARAMS*) + alloc_root(mem_root, sizeof(*param)))) + return true; + param->field= field; + param->name= name; + param->duplicate_counter= 0; + return rename_stat_fields.push_back(param, mem_root); + } + + bool collect_renamed_fields(THD *thd); + + /* Delete/update statistics in EITS tables */ + void apply_statistics_deletes_renames(THD *thd, TABLE *table); + private: // Type of ALTER TABLE algorithm. enum_alter_table_algorithm requested_algorithm; @@ -135,6 +213,10 @@ public: create_list.empty(); alter_index_ignorability_list.empty(); check_constraint_list.empty(); + drop_stat_fields.empty(); + drop_stat_indexes.empty(); + rename_stat_fields.empty(); + rename_stat_indexes.empty(); flags= 0; partition_flags= 0; keys_onoff= LEAVE_AS_IS; diff --git a/sql/sql_class.h b/sql/sql_class.h index c945d8d974f..d76cc46d307 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -444,7 +444,8 @@ private: class Key :public Sql_alloc, public DDL_options { public: - enum Keytype { PRIMARY, UNIQUE, MULTIPLE, FULLTEXT, SPATIAL, FOREIGN_KEY}; + enum Keytype { PRIMARY, UNIQUE, MULTIPLE, FULLTEXT, SPATIAL, FOREIGN_KEY, + IGNORE_KEY}; enum Keytype type; KEY_CREATE_INFO key_create_info; List columns; diff --git a/sql/sql_statistics.cc b/sql/sql_statistics.cc index 9351c1253b0..5cc85bd5d59 100644 --- a/sql/sql_statistics.cc +++ b/sql/sql_statistics.cc @@ -32,6 +32,7 @@ #include "uniques.h" #include "sql_show.h" #include "sql_partition.h" +#include "sql_alter.h" // RENAME_STAT_PARAMS /* The system variable 'use_stat_tables' can take one of the @@ -440,7 +441,7 @@ private: uint stat_key_length; /* Length of the key to access stat_table */ uchar *record[2]; /* Record buffers used to access/update stat_table */ - uint stat_key_idx; /* The number of the key to access stat_table */ + /* This is a helper function used only by the Stat_table constructors */ void common_init_stat_table() @@ -450,6 +451,7 @@ private: stat_key_idx= 0; stat_key_info= &stat_table->key_info[stat_key_idx]; stat_key_length= stat_key_info->key_length; + last_key_length= last_prefix_parts= 0; record[0]= stat_table->record[0]; record[1]= stat_table->record[1]; } @@ -466,29 +468,31 @@ protected: const LEX_CSTRING *db_name; /* Name of the database containing 'table' */ const LEX_CSTRING *table_name; /* Name of the table 'table' */ - void store_record_for_update() - { - store_record(stat_table, record[1]); - } + uchar last_key[MAX_KEY_LENGTH]; + uint last_key_length; + uint last_prefix_parts; void store_record_for_lookup() { DBUG_ASSERT(record[0] == stat_table->record[0]); } - bool update_record() + int update_record() { int err; if ((err= stat_file->ha_update_row(record[1], record[0])) && err != HA_ERR_RECORD_IS_THE_SAME) - return TRUE; - /* Make change permanent and avoid 'table is marked as crashed' errors */ - stat_file->extra(HA_EXTRA_FLUSH); - return FALSE; + return err; + return 0; } public: + uint stat_key_idx; /* The number of the key to access stat_table */ + void store_record_for_update() + { + store_record(stat_table, record[1]); + } /** @details @@ -579,13 +583,22 @@ public: bool find_stat() { - uchar key[MAX_KEY_LENGTH]; - key_copy(key, record[0], stat_key_info, stat_key_length); - return !stat_file->ha_index_read_idx_map(record[0], stat_key_idx, key, + last_key_length= stat_key_length; + key_copy(last_key, record[0], stat_key_info, stat_key_length); + return !stat_file->ha_index_read_idx_map(record[0], stat_key_idx, last_key, HA_WHOLE_KEY, HA_READ_KEY_EXACT); } - + void create_key_for_read(uint prefix_parts) + { + last_key_length= 0; + last_prefix_parts= prefix_parts; + for (uint i= 0; i < prefix_parts; i++) + last_key_length+= stat_key_info->key_part[i].store_length; + key_copy(last_key, record[0], stat_key_info, last_key_length); + } + + /** @brief Find a record in the statistical table by a key prefix value @@ -604,16 +617,32 @@ public: bool find_next_stat_for_prefix(uint prefix_parts) { - uchar key[MAX_KEY_LENGTH]; - uint prefix_key_length= 0; - for (uint i= 0; i < prefix_parts; i++) - prefix_key_length+= stat_key_info->key_part[i].store_length; - key_copy(key, record[0], stat_key_info, prefix_key_length); + create_key_for_read(prefix_parts); key_part_map prefix_map= (key_part_map) ((1 << prefix_parts) - 1); - return !stat_file->ha_index_read_idx_map(record[0], stat_key_idx, key, - prefix_map, HA_READ_KEY_EXACT); + return !stat_file->ha_index_read_idx_map(record[0], stat_key_idx, last_key, + prefix_map, HA_READ_KEY_EXACT); } + bool find_next_stat_for_prefix_with_next(uint prefix_parts) + { + create_key_for_read(prefix_parts); + key_part_map prefix_map= (key_part_map) ((1 << prefix_parts) - 1); + return !stat_file->ha_index_read_map(record[0], last_key, + prefix_map, + HA_READ_KEY_EXACT); + } + + /* + Read row with same key parts as last find_next_stat_for_prefix_with_next() + */ + + bool find_stat_with_next() + { + key_copy(last_key, record[0], stat_key_info, last_key_length); + key_part_map prefix_map= (key_part_map) ((1 << last_prefix_parts) - 1); + return !stat_file->ha_index_read_map(record[0], last_key, + prefix_map, HA_READ_KEY_EXACT); + } /** @brief @@ -646,7 +675,7 @@ public: bool res; store_record_for_update(); store_stat_fields(); - res= update_record(); + res= update_record() != 0; DBUG_ASSERT(res == 0); return res; } @@ -659,14 +688,11 @@ public: DBUG_ASSERT(0); return TRUE; } - /* Make change permanent and avoid 'table is marked as crashed' errors */ - stat_file->extra(HA_EXTRA_FLUSH); - } + } return FALSE; } - - /** + /** @brief Update the table name fields in the current record of stat_table @@ -690,7 +716,7 @@ public: { store_record_for_update(); change_full_table_name(db, tab); - bool rc= update_record(); + bool rc= update_record() != 0; store_record_for_lookup(); return rc; } @@ -715,10 +741,13 @@ public: int err; if ((err= stat_file->ha_delete_row(record[0]))) return TRUE; - /* Make change permanent and avoid 'table is marked as crashed' errors */ - stat_file->extra(HA_EXTRA_FLUSH); return FALSE; - } + } + + void flush() + { + stat_file->extra(HA_EXTRA_FLUSH); + } friend class Stat_table_write_iter; }; @@ -751,8 +780,8 @@ private: void change_full_table_name(const LEX_CSTRING *db, const LEX_CSTRING *tab) override { - db_name_field->store(db->str, db->length, system_charset_info); - table_name_field->store(tab->str, tab->length, system_charset_info); + db_name_field->store(db, system_charset_info); + table_name_field->store(tab, system_charset_info); } public: @@ -802,9 +831,8 @@ public: void set_key_fields() { - db_name_field->store(db_name->str, db_name->length, system_charset_info); - table_name_field->store(table_name->str, table_name->length, - system_charset_info); + db_name_field->store(db_name, system_charset_info); + table_name_field->store(table_name, system_charset_info); } @@ -894,8 +922,8 @@ private: void change_full_table_name(const LEX_CSTRING *db, const LEX_CSTRING *tab) override { - db_name_field->store(db->str, db->length, system_charset_info); - table_name_field->store(tab->str, tab->length, system_charset_info); + db_name_field->store(db, system_charset_info); + table_name_field->store(tab, system_charset_info); } public: @@ -939,9 +967,8 @@ public: void set_full_table_name() { - db_name_field->store(db_name->str, db_name->length, system_charset_info); - table_name_field->store(table_name->str, table_name->length, - system_charset_info); + db_name_field->store(db_name, system_charset_info); + table_name_field->store(table_name, system_charset_info); } @@ -959,18 +986,24 @@ public: It also sets table_field to the passed parameter. @note - The function is supposed to be called before any use of the + The function is supposed to be called before any use of the method find_stat for an object of the Column_stat class. */ void set_key_fields(Field *col) { set_full_table_name(); - column_name_field->store(col->field_name.str, col->field_name.length, - system_charset_info); + column_name_field->store(&col->field_name, system_charset_info); table_field= col; } + void set_key_fields(LEX_CSTRING *field_name) + { + set_full_table_name(); + column_name_field->store(field_name, system_charset_info); + table_field= 0; // Safety + } + /** @brief @@ -980,22 +1013,27 @@ public: The function updates the primary key fields containing database name, table name, and column name for the last found record in the statistical table column_stats. - + @retval - FALSE success with the update of the record + 0 success with the update of the record @retval - TRUE failure with the update of the record + # handler error in case of failure */ - bool update_column_key_part(const char *col) + int update_column_key_part(LEX_CSTRING *col) { + int rc; store_record_for_update(); - set_full_table_name(); - column_name_field->store(col, strlen(col), system_charset_info); - bool rc= update_record(); + rc= update_column(col); store_record_for_lookup(); return rc; - } + } + + int update_column(LEX_CSTRING *col) + { + column_name_field->store(col, system_charset_info); + return update_record(); + } /** @@ -1025,7 +1063,8 @@ public: { StringBuffer val; - MY_BITMAP *old_map= dbug_tmp_use_all_columns(stat_table, &stat_table->read_set); + MY_BITMAP *old_map= dbug_tmp_use_all_columns(stat_table, + &stat_table->read_set); for (uint i= COLUMN_STAT_MIN_VALUE; i <= COLUMN_STAT_HISTOGRAM; i++) { Field *stat_field= stat_table->field[i]; @@ -1210,8 +1249,7 @@ private: Field *table_name_field; /* Field for the column index_stats.table_name */ Field *index_name_field; /* Field for the column index_stats.table_name */ Field *prefix_arity_field; /* Field for the column index_stats.prefix_arity */ - - KEY *table_key_info; /* Info on the index to read/update statistics on */ + const KEY *table_key_info; /* Info on the index to read/update statistics on */ uint prefix_arity; /* Number of components of the index prefix of interest */ void common_init_index_stat_table() @@ -1225,8 +1263,8 @@ private: void change_full_table_name(const LEX_CSTRING *db, const LEX_CSTRING *tab) override { - db_name_field->store(db->str, db->length, system_charset_info); - table_name_field->store(tab->str, tab->length, system_charset_info); + db_name_field->store(db, system_charset_info); + table_name_field->store(tab, system_charset_info); } public: @@ -1273,9 +1311,13 @@ public: void set_full_table_name() { - db_name_field->store(db_name->str, db_name->length, system_charset_info); - table_name_field->store(table_name->str, table_name->length, - system_charset_info); + db_name_field->store(db_name, system_charset_info); + table_name_field->store(table_name, system_charset_info); + } + + inline void set_index_name(const LEX_CSTRING *name) + { + index_name_field->store(name, system_charset_info); } /** @@ -1295,12 +1337,10 @@ public: find_next_stat_for_prefix for an object of the Index_stat class. */ - void set_index_prefix_key_fields(KEY *index_info) + void set_index_prefix_key_fields(const KEY *index_info) { set_full_table_name(); - const char *index_name= index_info->name.str; - index_name_field->store(index_name, index_info->name.length, - system_charset_info); + set_index_name(&index_info->name); table_key_info= index_info; } @@ -1332,6 +1372,20 @@ public: } + int update_index_name(const LEX_CSTRING *name) + { + index_name_field->store(name, system_charset_info); + return update_record(); + } + + + int read_next() + { + return stat_table->file->ha_index_next_same(stat_table->record[0], + last_key, + last_key_length); + } + /** @brief Store statistical data into statistical fields of table index_stats @@ -1937,8 +1991,9 @@ public: @brief Create fields for min/max values to collect column statistics - @param - table Table the fields are created for + @param thd The thread handle + @param table Table the fields are created for + @param fields Fields for which we want to have statistics @details The function first allocates record buffers to store min/max values @@ -1958,7 +2013,8 @@ public: */ static -void create_min_max_statistical_fields_for_table(THD *thd, TABLE *table) +void create_min_max_statistical_fields_for_table(THD *thd, TABLE *table, + MY_BITMAP *fields) { uint rec_buff_length= table->s->rec_buff_length; @@ -1975,7 +2031,7 @@ void create_min_max_statistical_fields_for_table(THD *thd, TABLE *table) Field *fld; Field *table_field= *field_ptr; my_ptrdiff_t diff= record-table->record[0]; - if (!bitmap_is_set(table->read_set, table_field->field_index)) + if (!bitmap_is_set(fields, table_field->field_index)) continue; if (!(fld= table_field->clone(thd->mem_root, table, diff))) continue; @@ -2062,8 +2118,9 @@ create_min_max_statistical_fields(THD *thd, @brief Allocate memory for the table's statistical data to be collected - @param - table Table for which the memory for statistical data is allocated + @param thd The thread handle + @param table Table for which we should allocate statistical data + @param stat_fields Fields for which we want to have statistics @note The function allocates the memory for the statistical data on 'table' with @@ -2082,10 +2139,10 @@ create_min_max_statistical_fields(THD *thd, of the same table in parallel. */ -int alloc_statistics_for_table(THD* thd, TABLE *table) +int alloc_statistics_for_table(THD* thd, TABLE *table, MY_BITMAP *stat_fields) { Field **field_ptr; - uint fields= bitmap_bits_set(table->read_set); + uint fields= bitmap_bits_set(stat_fields); uint keys= table->s->keys; uint key_parts= table->s->ext_key_parts; uint hist_size= thd->variables.histogram_size; @@ -2122,7 +2179,7 @@ int alloc_statistics_for_table(THD* thd, TABLE *table) for (field_ptr= table->field; *field_ptr; field_ptr++) { - if (bitmap_is_set(table->read_set, (*field_ptr)->field_index)) + if (bitmap_is_set(stat_fields, (*field_ptr)->field_index)) { column_stats->histogram.set_size(hist_size); column_stats->histogram.set_type(hist_type); @@ -2154,7 +2211,7 @@ int alloc_statistics_for_table(THD* thd, TABLE *table) */ DBUG_ASSERT(idx_avg_frequency <= table_stats->idx_avg_frequency + key_parts); - create_min_max_statistical_fields_for_table(thd, table); + create_min_max_statistical_fields_for_table(thd, table, stat_fields); DBUG_RETURN(0); } @@ -2196,7 +2253,7 @@ int alloc_statistics_for_table(THD* thd, TABLE *table) Here the second and the third threads try to allocate the memory for statistical data at the same time. The precautions are taken to guarantee the correctness of the allocation. -*/ +*/ static int alloc_engine_independent_statistics(THD *thd, const TABLE_SHARE *table_share, @@ -2686,10 +2743,8 @@ int collect_statistics_for_table(THD *thd, TABLE *table) @brief Update statistics for a table in the persistent statistical tables - @param - thd The thread handle - @param - table The table to collect statistics on + @param thd The thread handle + @param table The table to collect statistics on @details For each statistical table st the function looks for the rows from this @@ -2734,7 +2789,10 @@ int update_statistics_for_table(THD *thd, TABLE *table) start_new_trans new_trans(thd); if (open_stat_tables(thd, tables, TRUE)) - DBUG_RETURN(rc); + { + new_trans.restore_old_transaction(); + DBUG_RETURN(0); + } /* Ensure that no one is reading satistics while we are writing them @@ -2788,12 +2846,16 @@ int update_statistics_for_table(THD *thd, TABLE *table) } } + tables[TABLE_STAT].table->file->extra(HA_EXTRA_FLUSH); + tables[COLUMN_STAT].table->file->extra(HA_EXTRA_FLUSH); + tables[INDEX_STAT].table->file->extra(HA_EXTRA_FLUSH); + thd->restore_stmt_binlog_format(save_binlog_format); if (thd->commit_whole_transaction_and_close_tables()) rc= 1; - new_trans.restore_old_transaction(); mysql_mutex_unlock(&table->s->LOCK_statistics); + new_trans.restore_old_transaction(); DBUG_RETURN(rc); } @@ -3058,6 +3120,7 @@ void TABLE_STATISTICS_CB::update_stats_in_table(TABLE *table) int read_statistics_for_tables(THD *thd, TABLE_LIST *tables, bool force_reload) { + int rc= 0; TABLE_LIST stat_tables[STATISTICS_TABLES]; bool found_stat_table= false; bool statistics_for_tables_is_needed= false; @@ -3122,7 +3185,10 @@ read_statistics_for_tables(THD *thd, TABLE_LIST *tables, bool force_reload) start_new_trans new_trans(thd); if (open_stat_tables(thd, stat_tables, FALSE)) - DBUG_RETURN(1); + { + rc= 1; + goto end; + } for (TABLE_LIST *tl= tables; tl; tl= tl->next_global) { @@ -3174,9 +3240,10 @@ read_statistics_for_tables(THD *thd, TABLE_LIST *tables, bool force_reload) } thd->commit_whole_transaction_and_close_tables(); - new_trans.restore_old_transaction(); - DBUG_RETURN(0); +end: + new_trans.restore_old_transaction(); + DBUG_RETURN(rc); } @@ -3216,9 +3283,12 @@ int delete_statistics_for_table(THD *thd, const LEX_CSTRING *db, DBUG_ENTER("delete_statistics_for_table"); start_new_trans new_trans(thd); - + if (open_stat_tables(thd, tables, TRUE)) + { + new_trans.restore_old_transaction(); DBUG_RETURN(0); + } save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); @@ -3259,10 +3329,14 @@ int delete_statistics_for_table(THD *thd, const LEX_CSTRING *db, if (err & !rc) rc= 1; + tables[TABLE_STAT].table->file->extra(HA_EXTRA_FLUSH); + tables[COLUMN_STAT].table->file->extra(HA_EXTRA_FLUSH); + tables[INDEX_STAT].table->file->extra(HA_EXTRA_FLUSH); + thd->restore_stmt_binlog_format(save_binlog_format); thd->commit_whole_transaction_and_close_tables(); - new_trans.restore_old_transaction(); + new_trans.restore_old_transaction(); DBUG_RETURN(rc); } @@ -3299,7 +3373,10 @@ int delete_statistics_for_column(THD *thd, TABLE *tab, Field *col) start_new_trans new_trans(thd); if (open_stat_table_for_ddl(thd, &tables, &stat_table_name[1])) - DBUG_RETURN(0); + { + new_trans.restore_old_transaction(); + DBUG_RETURN(0); // Not an error + } save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); @@ -3313,11 +3390,202 @@ int delete_statistics_for_column(THD *thd, TABLE *tab, Field *col) rc= 1; } + column_stat.flush(); thd->restore_stmt_binlog_format(save_binlog_format); if (thd->commit_whole_transaction_and_close_tables()) rc= 1; - new_trans.restore_old_transaction(); + new_trans.restore_old_transaction(); + DBUG_RETURN(rc); +} + + +/** + Generate tempoary column or index name for renames +*/ + +static LEX_CSTRING *generate_tmp_name(LEX_CSTRING *to, uint counter) +{ + char *res=int10_to_str(counter, strmov((char*) to->str, "#sql_tmp_name#"), + 10); + /* + Include an end zero in the tmp name to avoid any possible conflict + with existing column names. + */ + to->length= (size_t) (res - to->str) + 1; + return to; +} + + +/** + Rename a set of columns in the statistical table column_stats + + @param thd The thread handle + @param tab The table the column belongs to + @param fields List of fields and names to be renamed + + @details + The function replaces the names of the columns in fields that belongs + to the table 'tab' in the statistical table column_stats. + + @retval 0 If update was successful, tmp table or could not open stat table + @retval -1 Commit failed + @retval >0 Error number from engine + + @note + The function is called when executing any statement that renames a column, + but does not change the column definition. +*/ + +int rename_columns_in_stat_table(THD *thd, TABLE *tab, + List + *fields) +{ + int err; + enum_binlog_format save_binlog_format; + TABLE *stat_table; + TABLE_LIST tables; + int rc= 0; + uint duplicate_counter= 0; + uint org_elements= fields->elements+1; + List_iterator it(*fields); + char tmp_name_buffer[32]; + LEX_CSTRING tmp_name= {tmp_name_buffer, 0}; + DBUG_ENTER("rename_column_in_stat_tables"); + + if (tab->s->tmp_table != NO_TMP_TABLE) + DBUG_RETURN(0); + + start_new_trans new_trans(thd); + + if (open_stat_table_for_ddl(thd, &tables, &stat_table_name[1])) + { + new_trans.restore_old_transaction(); + DBUG_RETURN(0); + } + + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); + + /* Rename column in the statistical table table_stat */ + + stat_table= tables.table; + + /* Loop until fields is empty or previous round did nothing */ + while (!fields->is_empty() && fields->elements != org_elements) + { + Alter_info::RENAME_COLUMN_STAT_PARAMS *field; + org_elements= fields->elements; + it.rewind(); + while ((field= it++)) + { + Column_stat column_stat(stat_table, tab); + LEX_CSTRING *from_name; + from_name= (!field->duplicate_counter ? + &field->field->field_name : + generate_tmp_name(&tmp_name, + field->duplicate_counter)); + column_stat.set_key_fields(from_name); + if (column_stat.find_stat()) + { + err= column_stat.update_column_key_part(field->name); + if (likely(err != HA_ERR_FOUND_DUPP_KEY)) + it.remove(); + else if (!field->duplicate_counter) + { + /* + This is probably an ALTER TABLE of type rename a->b, b->a + Rename the column to a temporary name + */ + LEX_CSTRING *new_name= + generate_tmp_name(&tmp_name, ++duplicate_counter); + field->duplicate_counter= duplicate_counter; + + if ((err= column_stat.update_column(new_name))) + { + if (likely(err != HA_ERR_FOUND_DUPP_KEY)) + { + DBUG_ASSERT(0); + it.remove(); // Unknown error, ignore column + } + else + { + /* + The only way this could happen is if the table has a column + with same name as the temporary column name, probably from a + failed alter table. + Remove the conflicting row and update it again. + */ + if (!column_stat.find_stat()) + DBUG_ASSERT(0); + else if (column_stat.delete_stat()) + DBUG_ASSERT(0); + else + { + column_stat.set_key_fields(from_name); + if (!column_stat.find_stat()) + DBUG_ASSERT(0); + else if (column_stat.update_column_key_part(&tmp_name)) + DBUG_ASSERT(0); + } + } + } + } + } + else /* column_stat.find_stat() */ + { + /* Statistics for the field did not exists */ + it.remove(); + } + } + } + + if (!fields->is_empty()) + { + /* + All unhandled renamed fields has now a temporary name. + Remove all conflicing rows and rename the temporary name to + the final name. + */ + + Alter_info::RENAME_COLUMN_STAT_PARAMS *field; + it.rewind(); + while ((field= it++)) + { + Column_stat column_stat(stat_table, tab); + DBUG_ASSERT(field->duplicate_counter); + + /* Remove the conflicting row */ + column_stat.set_key_fields(field->name); + if (column_stat.find_stat()) + { + int err __attribute__((unused)); + err= column_stat.delete_stat(); + DBUG_ASSERT(err == 0); + } + + /* Restore saved row with old statistics to new name */ + column_stat. + set_key_fields(generate_tmp_name(&tmp_name, + field->duplicate_counter)); + if (column_stat.find_stat()) + { + int err __attribute__((unused)); + err= column_stat.update_column_key_part(field->name); + DBUG_ASSERT(err == 0); + } + else + { + DBUG_ASSERT(0); + } + } + } + + stat_table->file->extra(HA_EXTRA_FLUSH); + thd->restore_stmt_binlog_format(save_binlog_format); + if (thd->commit_whole_transaction_and_close_tables()) + rc= -1; + + new_trans.restore_old_transaction(); DBUG_RETURN(rc); } @@ -3359,7 +3627,10 @@ int delete_statistics_for_index(THD *thd, TABLE *tab, KEY *key_info, start_new_trans new_trans(thd); if (open_stat_table_for_ddl(thd, &tables, &stat_table_name[2])) - DBUG_RETURN(0); + { + new_trans.restore_old_transaction(); + DBUG_RETURN(0); // Not an error + } save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); @@ -3393,11 +3664,194 @@ int delete_statistics_for_index(THD *thd, TABLE *tab, KEY *key_info, if (err && !rc) rc= 1; + /* Make change permanent and avoid 'table is marked as crashed' errors */ + index_stat.flush(); + thd->restore_stmt_binlog_format(save_binlog_format); if (thd->commit_whole_transaction_and_close_tables()) rc= 1; - new_trans.restore_old_transaction(); + new_trans.restore_old_transaction(); + DBUG_RETURN(rc); +} + + +/** + Rename a set of indexes in the statistical table index_stats + + @param thd The thread handle + @param tab The table the indexes belongs to + @param fields List of indexes to be renamed + + @details + The function replaces the names of the indexe in fields that belongs + to the table 'tab' in the statistical table index_stats. + + @retval 0 If update was successful, tmp table or could not open stat table + @retval -1 Commit failed + @retval >0 Error number from engine + + @note + The function is called when executing any statement that renames a column, + but does not change the column definition. +*/ + +int rename_indexes_in_stat_table(THD *thd, TABLE *tab, + List + *indexes) +{ + int err; + enum_binlog_format save_binlog_format; + TABLE *stat_table; + TABLE_LIST tables; + int rc= 0; + uint duplicate_counter= 0; + List_iterator it(*indexes); + Alter_info::RENAME_INDEX_STAT_PARAMS *index; + char tmp_name_buffer[32]; + LEX_CSTRING tmp_name= {tmp_name_buffer, 0}; + DBUG_ENTER("rename_indexes_in_stat_tables"); + + if (tab->s->tmp_table != NO_TMP_TABLE) + DBUG_RETURN(0); + + start_new_trans new_trans(thd); + + if (open_stat_table_for_ddl(thd, &tables, &stat_table_name[2])) + { + new_trans.restore_old_transaction(); + DBUG_RETURN(0); + } + + save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); + + /* Rename index in the statistical table index_stat */ + + stat_table= tables.table; + + /* + Loop over all indexes and rename to new name or temp name in case of + conflicts + */ + + while ((index= it++)) + { + Index_stat index_stat(stat_table, tab); + uint found= 0; + + /* We have to make a loop as one index may have many entries */ + for (;;) + { + index_stat.set_index_prefix_key_fields(index->key); + if (!index_stat.find_next_stat_for_prefix(3)) + break; + index_stat.store_record_for_update(); + err= index_stat.update_index_name(index->name); + + if (unlikely(err == HA_ERR_FOUND_DUPP_KEY)) + { + /* + This is probably an ALTER TABLE of type rename a->b, b->a + Rename the column to a temporary name + */ + if (!found++) + ++duplicate_counter; + index->duplicate_counter= duplicate_counter; + index->usage_count++; + if ((err= index_stat.update_index_name(generate_tmp_name(&tmp_name, duplicate_counter)))) + { + if (err != HA_ERR_FOUND_DUPP_KEY) + { + DBUG_ASSERT(0); + } + else + { + /* + The only way this could happen is if the table has an index + with same name as the temporary column index, probably from a + failed alter table. + Remove the conflicting row and update it again. + */ + if (!index_stat.find_stat()) + DBUG_ASSERT(0); + else if (index_stat.delete_stat()) + DBUG_ASSERT(0); + else + { + index_stat.set_index_prefix_key_fields(index->key); + if (!index_stat.find_stat()) + DBUG_ASSERT(0); + else + { + index_stat.store_record_for_update(); + if (index_stat.update_index_name(&tmp_name)) + DBUG_ASSERT(0); + } + } + } + } + } + } + if (!found) + it.remove(); // All renames succeded + } + + if (!indexes->is_empty()) + { + /* + All unhandled renamed index has now a temporary name. + Remove all conflicing rows and rename the temporary name to + the final name. + */ + + Alter_info::RENAME_INDEX_STAT_PARAMS *index; + it.rewind(); + Index_stat index_stat(stat_table, tab); + stat_table->file->ha_index_init(index_stat.stat_key_idx, 0); + + while ((index= it++)) + { + int err __attribute__((unused)); + + /* Remove the conflicting rows */ + index_stat.set_index_prefix_key_fields(index->key); + index_stat.set_index_name(index->name); + + if (index_stat.find_next_stat_for_prefix_with_next(3)) + { + do + { + err= index_stat.delete_stat(); + DBUG_ASSERT(err == 0); + } + while (index_stat.read_next() == 0); + } + + /* Restore saved row with old statistics to new name */ + index_stat.set_index_name(generate_tmp_name(&tmp_name, + index->duplicate_counter)); + if (!index_stat.find_stat_with_next()) + DBUG_ASSERT(0); + else + { + uint updated= 0; + do + { + index_stat.store_record_for_update(); + err= index_stat.update_index_name(index->name); + DBUG_ASSERT(err == 0); + } while (++updated < index->usage_count && index_stat.read_next() == 0); + } + } + stat_table->file->ha_index_end(); + } + + stat_table->file->extra(HA_EXTRA_FLUSH); + thd->restore_stmt_binlog_format(save_binlog_format); + if (thd->commit_whole_transaction_and_close_tables()) + rc= -1; + + new_trans.restore_old_transaction(); DBUG_RETURN(rc); } @@ -3444,7 +3898,10 @@ int rename_table_in_stat_tables(THD *thd, const LEX_CSTRING *db, start_new_trans new_trans(thd); if (open_stat_tables(thd, tables, TRUE)) - DBUG_RETURN(0); // not an error + { + new_trans.restore_old_transaction(); + DBUG_RETURN(0); + } save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); @@ -3492,71 +3949,15 @@ int rename_table_in_stat_tables(THD *thd, const LEX_CSTRING *db, rc= 1; } - thd->restore_stmt_binlog_format(save_binlog_format); - if (thd->commit_whole_transaction_and_close_tables()) - rc= 1; - new_trans.restore_old_transaction(); - - DBUG_RETURN(rc); -} - - -/** - Rename a column in the statistical table column_stats - - @param thd The thread handle - @param tab The table the column belongs to - @param col The column to be renamed - @param new_name The new column name - - @details - The function replaces the name of the column 'col' belonging to the table - 'tab' for 'new_name' in the statistical table column_stats. - - @retval 0 If all updates of the table name are successful - @retval 1 Otherwise - - @note - The function is called when executing any statement that renames a column, - but does not change the column definition. -*/ - -int rename_column_in_stat_tables(THD *thd, TABLE *tab, Field *col, - const char *new_name) -{ - int err; - enum_binlog_format save_binlog_format; - TABLE *stat_table; - TABLE_LIST tables; - int rc= 0; - DBUG_ENTER("rename_column_in_stat_tables"); - - if (tab->s->tmp_table != NO_TMP_TABLE) - DBUG_RETURN(0); - - start_new_trans new_trans(thd); - - if (open_stat_table_for_ddl(thd, &tables, &stat_table_name[1])) - DBUG_RETURN(rc); - - save_binlog_format= thd->set_current_stmt_binlog_format_stmt(); - - /* Rename column in the statistical table table_stat */ - stat_table= tables.table; - Column_stat column_stat(stat_table, tab); - column_stat.set_key_fields(col); - if (column_stat.find_stat()) - { - err= column_stat.update_column_key_part(new_name); - if (err & !rc) - rc= 1; - } + tables[TABLE_STAT].table->file->extra(HA_EXTRA_FLUSH); + tables[COLUMN_STAT].table->file->extra(HA_EXTRA_FLUSH); + tables[INDEX_STAT].table->file->extra(HA_EXTRA_FLUSH); thd->restore_stmt_binlog_format(save_binlog_format); if (thd->commit_whole_transaction_and_close_tables()) rc= 1; - new_trans.restore_old_transaction(); + new_trans.restore_old_transaction(); DBUG_RETURN(rc); } diff --git a/sql/sql_statistics.h b/sql/sql_statistics.h index f5ccb26af7b..5c98451808a 100644 --- a/sql/sql_statistics.h +++ b/sql/sql_statistics.h @@ -119,16 +119,21 @@ int read_statistics_for_tables_if_needed(THD *thd, TABLE_LIST *tables); int read_statistics_for_tables(THD *thd, TABLE_LIST *tables, bool force_reload); int collect_statistics_for_table(THD *thd, TABLE *table); -int alloc_statistics_for_table(THD *thd, TABLE *table); +int alloc_statistics_for_table(THD *thd, TABLE *table, MY_BITMAP *stat_fields); int update_statistics_for_table(THD *thd, TABLE *table); -int delete_statistics_for_table(THD *thd, const LEX_CSTRING *db, const LEX_CSTRING *tab); +int delete_statistics_for_table(THD *thd, const LEX_CSTRING *db, + const LEX_CSTRING *tab); int delete_statistics_for_column(THD *thd, TABLE *tab, Field *col); int delete_statistics_for_index(THD *thd, TABLE *tab, KEY *key_info, bool ext_prefixes_only); -int rename_table_in_stat_tables(THD *thd, const LEX_CSTRING *db, const LEX_CSTRING *tab, - const LEX_CSTRING *new_db, const LEX_CSTRING *new_tab); -int rename_column_in_stat_tables(THD *thd, TABLE *tab, Field *col, - const char *new_name); +int rename_table_in_stat_tables(THD *thd, const LEX_CSTRING *db, + const LEX_CSTRING *tab, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_tab); +int rename_columns_in_stat_table(THD *thd, TABLE *tab, + List *fields); +int rename_indexes_in_stat_table(THD *thd, TABLE *tab, + List *indexes); void set_statistics_for_table(THD *thd, TABLE *table); double get_column_avg_frequency(Field * field); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index af365d6e7f6..9e20db8b5e4 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -2803,8 +2803,6 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, bool primary_key=0,unique_key=0; Key *key, *key2; uint tmp, key_number; - /* special marker for keys to be ignored */ - static char ignore_key[1]; /* Calculate number of key segements */ *key_count= 0; @@ -2852,17 +2850,17 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, both, is 'generated', and a generated key is a prefix of the other key. Then we do not need the generated shorter key. */ - if (key2->type != Key::FOREIGN_KEY && key2->name.str != ignore_key && + if (key2->type != Key::FOREIGN_KEY && key2->type != Key::IGNORE_KEY && is_foreign_key_prefix(key, key2)) { /* mark that the generated key should be ignored */ if (!key2->generated || (key->generated && key->columns.elements < key2->columns.elements)) - key->name.str= ignore_key; + key->type= Key::IGNORE_KEY; else { - key2->name.str= ignore_key; + key2->type= Key::IGNORE_KEY; key_parts-= key2->columns.elements; (*key_count)--; } @@ -2870,7 +2868,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, } } } - if (key->name.str != ignore_key) + if (key->type != Key::IGNORE_KEY) key_parts+=key->columns.elements; else (*key_count)--; @@ -2900,7 +2898,14 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, key_iterator.rewind(); while ((key=key_iterator++)) { - if (key->name.str == ignore_key || key->type == Key::FOREIGN_KEY) + if (key->type == Key::IGNORE_KEY) + { + /* The key was replaced by another key */ + if (alter_info->add_stat_drop_index(thd, &key->name)) + DBUG_RETURN(true); + continue; + } + if (key->type == Key::FOREIGN_KEY) continue; /* Create the key name based on the first column (if not given) */ if (key->type == Key::PRIMARY) @@ -2962,12 +2967,12 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, Key_part_spec *column; is_hash_field_needed= false; - if (key->name.str == ignore_key) + if (key->type == Key::IGNORE_KEY) { /* ignore redundant keys */ do key=key_iterator++; - while (key && key->name.str == ignore_key); + while (key && key->type == Key::IGNORE_KEY); if (!key) break; } @@ -2995,6 +3000,9 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, case Key::FOREIGN_KEY: key_number--; // Skip this key continue; + case Key::IGNORE_KEY: + DBUG_ASSERT(0); + break; default: key_info->flags = HA_NOSAME; break; @@ -3198,6 +3206,8 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, key_add_part_check_null(file, key_info, sql_field, column)) DBUG_RETURN(TRUE); break; + case Key::IGNORE_KEY: + break; } if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER) @@ -6445,7 +6455,9 @@ static bool fill_alter_inplace_info(THD *thd, TABLE *table, bool varchar, if (table->s->tmp_table == NO_TMP_TABLE) { - delete_statistics_for_column(thd, table, field); + if (alter_info->drop_stat_fields.push_back(field, thd->mem_root)) + DBUG_RETURN(true); + KEY *key_info= table->key_info; for (uint i= 0; i < table->s->keys; i++, key_info++) { @@ -6457,9 +6469,10 @@ static bool fill_alter_inplace_info(THD *thd, TABLE *table, bool varchar, { if (key_info->key_part[j].fieldnr - 1 == field->field_index) { - delete_statistics_for_index( - thd, table, key_info, - j >= key_info->user_defined_key_parts); + if (alter_info->add_stat_drop_index(key_info, + j >= key_info->user_defined_key_parts, + thd->mem_root)) + DBUG_RETURN(true); break; } } @@ -6516,13 +6529,17 @@ static bool fill_alter_inplace_info(THD *thd, TABLE *table, bool varchar, ha_alter_info->handler_flags|= ALTER_STORED_COLUMN_TYPE; } - /* Check if field was renamed (case-sensitive for detecting case change) */ + /* + Check if field was renamed (case-sensitive for detecting case change) + */ if (cmp(&field->field_name, &new_field->field_name)) { field->flags|= FIELD_IS_RENAMED; ha_alter_info->handler_flags|= ALTER_COLUMN_NAME; - rename_column_in_stat_tables(thd, table, field, - new_field->field_name.str); + if (alter_info->add_stat_rename_field(field, + &new_field->field_name, + thd->mem_root)) + DBUG_RETURN(true); } /* Check that NULL behavior is same for old and new fields */ @@ -7481,6 +7498,8 @@ static bool mysql_inplace_alter_table(THD *thd, in case of crash it should use the new one and log the query to the binary log. */ + ha_alter_info->alter_info->apply_statistics_deletes_renames(thd, table); + ddl_log_update_phase(ddl_log_state, DDL_ALTER_TABLE_PHASE_INPLACE_COPIED); debug_crash_here("ddl_log_alter_after_log"); @@ -7585,6 +7604,7 @@ static bool mysql_inplace_alter_table(THD *thd, DBUG_RETURN(true); } + /** maximum possible length for certain blob types. @@ -7833,7 +7853,10 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, create_info->used_fields|=HA_CREATE_USED_AUTO; } if (table->s->tmp_table == NO_TMP_TABLE) - (void) delete_statistics_for_column(thd, table, field); + { + if (alter_info->drop_stat_fields.push_back(field, thd->mem_root)) + DBUG_RETURN(true); + } dropped_sys_vers_fields|= field->flags; drop_it.remove(); dropped_fields= &table->tmp_set; @@ -7845,13 +7868,19 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, { vers_system_invisible= true; } - /* invisible versioning column is dropped automatically on DROP SYSTEM VERSIONING */ + /* + invisible versioning column is dropped automatically on + DROP SYSTEM VERSIONING + */ if (!drop && field->invisible >= INVISIBLE_SYSTEM && field->flags & VERS_SYSTEM_FIELD && alter_info->flags & ALTER_DROP_SYSTEM_VERSIONING) { if (table->s->tmp_table == NO_TMP_TABLE) - (void) delete_statistics_for_column(thd, table, field); + { + if (alter_info->drop_stat_fields.push_back(field, thd->mem_root)) + DBUG_RETURN(true); + } continue; } @@ -8214,19 +8243,24 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, { if (table->s->tmp_table == NO_TMP_TABLE) { - (void) delete_statistics_for_index(thd, table, key_info, FALSE); + if (alter_info->add_stat_drop_index(key_info, FALSE, thd->mem_root)) + DBUG_RETURN(true); if (primary_key) { KEY *tab_key_info= table->key_info; for (uint j=0; j < table->s->keys; j++, tab_key_info++) { - if (tab_key_info->user_defined_key_parts != + if (tab_key_info != key_info && + tab_key_info->user_defined_key_parts != tab_key_info->ext_key_parts) - (void) delete_statistics_for_index(thd, table, tab_key_info, - TRUE); - } + { + if (alter_info->add_stat_drop_index(tab_key_info, TRUE, + thd->mem_root)) + DBUG_RETURN(true); + } + } } - } + } drop_it.remove(); continue; } @@ -8263,8 +8297,11 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, goto err; } - key_name= rename_key->new_name.str; + key_name= rename_key->new_name.str; // New name of current key_info rename_key_it.remove(); + alter_info->add_stat_rename_index(key_info, &rename_key->new_name, + thd->mem_root); + /* If the user has explicitly renamed the key, we should no longer treat it as generated. Otherwise this key might be automatically @@ -8374,10 +8411,17 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, if (table->s->tmp_table == NO_TMP_TABLE) { if (delete_index_stat) - (void) delete_statistics_for_index(thd, table, key_info, FALSE); + { + if (alter_info->add_stat_drop_index(key_info, FALSE, thd->mem_root)) + DBUG_RETURN(true); + } else if (alter_ctx->modified_primary_key && key_info->user_defined_key_parts != key_info->ext_key_parts) - (void) delete_statistics_for_index(thd, table, key_info, TRUE); + { + if (alter_info->add_stat_drop_index(key_info, FALSE, + thd->mem_root)) + DBUG_RETURN(true); + } } if (!user_keyparts && key_parts.elements) @@ -10196,6 +10240,13 @@ do_continue:; alter_info->flags|= ALTER_INDEX_ORDER; create_info->alias= alter_ctx.table_name; thd->abort_on_warning= !ignore && thd->is_strict_mode(); + + /* + This is to be able to call Alter_info::add_stat_drop_index(thd, key_name) + from mysql_prepare_create_table() + */ + alter_info->original_table= table; + /* Create the .frm file for the new table. Storage engine table will not be created at this stage. @@ -10466,6 +10517,16 @@ do_continue:; thd->count_cuted_fields= CHECK_FIELD_WARN; // calc cuted fields thd->cuted_fields=0L; + /* + Collect fields that was renamed. + We do not do that if fill_alter_inplace_info() has + already collected renamed fields. + */ + if (alter_info->flags & (ALTER_CHANGE_COLUMN | ALTER_RENAME_COLUMN) && + alter_info->rename_stat_fields.is_empty()) + if (alter_info->collect_renamed_fields(thd)) + goto err_new_table_cleanup; + /* We do not copy data for MERGE tables. Only the children have data. MERGE tables have HA_NO_COPY_ON_ALTER set. @@ -10662,6 +10723,9 @@ do_continue:; if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME)) goto err_new_table_cleanup; + /* Now we are the only user. Update the data in EITS tables */ + alter_info->apply_statistics_deletes_renames(thd, table); + close_all_tables_for_name(thd, table->s, alter_ctx.is_table_renamed() ? HA_EXTRA_PREPARE_FOR_RENAME: diff --git a/sql/structs.h b/sql/structs.h index 0e4ca6236fe..2818d43a06a 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -95,11 +95,11 @@ class engine_option_value; struct ha_index_option_struct; typedef struct st_key { - uint key_length; /* total length of user defined key parts */ - ulong flags; /* dupp key and pack flags */ - uint user_defined_key_parts; /* How many key_parts */ - uint usable_key_parts; /* Should normally be = user_defined_key_parts */ - uint ext_key_parts; /* Number of key parts in extended key */ + uint key_length; /* total length of user defined key parts */ + ulong flags; /* dupp key and pack flags */ + uint user_defined_key_parts; /* How many key_parts */ + uint usable_key_parts; /* Should normally be = user_defined_key_parts */ + uint ext_key_parts; /* Number of key parts in extended key */ ulong ext_key_flags; /* Flags for extended key */ /* Parts of primary key that are in the extension of this index.