Commit 3a3d5ba2 authored by Eugene Kosov's avatar Eugene Kosov Committed by Sergei Golubchik

MDEV-13301 Optimize DROP INDEX, ADD INDEX into RENAME INDEX

Just rename index in data dictionary and in InnoDB cache when it's possible.
Introduce ALTER_INDEX_RENAME for that purpose so that engines can optimize
such operation.

Unused code between macro MYSQL_RENAME_INDEX was removed.

compare_keys_but_name(): compare index definitions except for index names

Alter_inplace_info::rename_keys:
ha_innobase_inplace_ctx::rename_keys: vector of rename indexes

fill_alter_inplace_info():: fills Alter_inplace_info::rename_keys
parent 5d8ca989
......@@ -2,7 +2,7 @@ CREATE TABLE t1 (x INT NOT NULL UNIQUE KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES(5);
SET GLOBAL innodb_log_checkpoint_now=TRUE;
SET DEBUG_SYNC='commit_cache_rebuild SIGNAL ready WAIT_FOR finish';
ALTER TABLE t1 ADD PRIMARY KEY(x);
ALTER TABLE t1 FORCE;;
connect con1,localhost,root,,;
SET DEBUG_SYNC='now WAIT_FOR ready';
SET GLOBAL innodb_log_checkpoint_now=TRUE;
......@@ -13,7 +13,6 @@ SHOW CREATE TABLE t1;
Table Create Table
t1 CREATE TABLE `t1` (
`x` int(11) NOT NULL,
PRIMARY KEY (`x`),
UNIQUE KEY `x` (`x`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
DROP TABLE t1;
......
......@@ -236,8 +236,7 @@ WHERE variable_name = 'innodb_encryption_n_rowlog_blocks_encrypted');
connection con1;
SET DEBUG_SYNC = 'row_log_apply_before SIGNAL c2e_created WAIT_FOR dml2_done';
SET lock_wait_timeout = 10;
ALTER TABLE t1 DROP INDEX c2d, ADD INDEX c2e(c2),
ALGORITHM = INPLACE;
ALTER TABLE t1 CHANGE c2 c22 INT, DROP INDEX c2d, ADD INDEX c2e(c22, c3(10)), ALGORITHM = NOCOPY;
connection default;
INSERT INTO t1 SELECT 80 + c1, c2, c3 FROM t1;
INSERT INTO t1 SELECT 160 + c1, c2, c3 FROM t1;
......@@ -295,6 +294,7 @@ INNER JOIN INFORMATION_SCHEMA.INNODB_SYS_FIELDS sf
ON si.index_id = sf.index_id WHERE si.name = '?c2e';
name pos
c2 0
c3 1
SET @merge_encrypt_1=
(SELECT variable_value FROM information_schema.global_status
WHERE variable_name = 'innodb_encryption_n_merge_blocks_encrypted');
......
create function get_index_id(tbl_id int, index_name char(100))
returns int
begin
declare res int;
select index_id into res from information_schema.innodb_sys_indexes where
name=index_name and table_id = tbl_id;
return res;
end|
create table t (
pk int primary key,
a int,
b int,
c int,
unique index a_key (a),
key c_key (c)
) engine=innodb stats_persistent=1;
insert into t values (1, 1, 1, 1);
set @table_id = (select table_id from information_schema.innodb_sys_tables where name='test/t');
set @a_key_id = get_index_id(@table_id, 'a_key');
set @c_key_id = get_index_id(@table_id, 'c_key');
set @primary_id = get_index_id(@table_id, 'primary');
select distinct(index_name) from mysql.innodb_index_stats where table_name = 't';
index_name
PRIMARY
a_key
c_key
alter table t
drop index a_key,
add unique index a_key_strikes_back (a);
select distinct(index_name) from mysql.innodb_index_stats where table_name = 't';
index_name
PRIMARY
a_key_strikes_back
c_key
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select @a_key_id = get_index_id(@table_id, 'a_key_strikes_back'),
@c_key_id = get_index_id(@table_id, 'c_key'),
@primary_id = get_index_id(@table_id, 'primary');
@a_key_id = get_index_id(@table_id, 'a_key_strikes_back') @c_key_id = get_index_id(@table_id, 'c_key') @primary_id = get_index_id(@table_id, 'primary')
1 1 1
set @a_key_strikes_back_id = get_index_id(@table_id, 'a_key_strikes_back');
set @c_key_id = get_index_id(@table_id, 'c_key');
set @primary_id = get_index_id(@table_id, 'primary');
alter table t
drop index a_key_strikes_back,
add unique index a_key_returns (a),
drop primary key,
add primary key (pk),
add unique index b_key (b);
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select @a_key_strikes_back_id = get_index_id(@table_id, 'a_key_returns'),
@c_key_id = get_index_id(@table_id, 'c_key'),
@primary_id = get_index_id(@table_id, 'primary');
@a_key_strikes_back_id = get_index_id(@table_id, 'a_key_returns') @c_key_id = get_index_id(@table_id, 'c_key') @primary_id = get_index_id(@table_id, 'primary')
1 1 1
set @a_key_returns_id = get_index_id(@table_id, 'a_key_returns');
set @b_key_id = get_index_id(@table_id, 'b_key');
set @c_key_id = get_index_id(@table_id, 'c_key');
set @primary_id = get_index_id(@table_id, 'primary');
alter table t
drop key c_key,
add key c_key2 (c);
check table t;
Table Op Msg_type Msg_text
test.t check status OK
select @a_key_returns_id = get_index_id(@table_id, 'a_key_returns'),
@b_key_id = get_index_id(@table_id, 'b_key'),
@c_key_id = get_index_id(@table_id, 'c_key2'),
@primary_id = get_index_id(@table_id, 'primary');
@a_key_returns_id = get_index_id(@table_id, 'a_key_returns') @b_key_id = get_index_id(@table_id, 'b_key') @c_key_id = get_index_id(@table_id, 'c_key2') @primary_id = get_index_id(@table_id, 'primary')
1 1 1 1
drop table t;
drop function get_index_id;
create table errors (
a int,
unique key a_key (a),
b int
) engine=innodb;
alter table errors
drop key a_key,
drop key a_key,
add unique key a_key2 (a);
ERROR 42000: Can't DROP INDEX `a_key`; check that it exists
alter table errors
drop key a_key,
drop key a_key2,
add unique key a_key2 (a);
ERROR 42000: Can't DROP INDEX `a_key2`; check that it exists
alter table errors
add key b_key (b),
drop key b_key,
add key bb_key (b);
ERROR 42000: Can't DROP INDEX `b_key`; check that it exists
alter table errors
drop key a_key,
add key a_key2 (a),
drop key a_key,
add key a_key2 (a);
ERROR 42000: Can't DROP INDEX `a_key`; check that it exists
drop table errors;
create table corrupted (
a int,
key a_key (a)
) engine=innodb;
insert into corrupted values (1);
select * from corrupted;
a
1
SET @save_dbug = @@SESSION.debug_dbug;
SET debug_dbug = '+d,dict_set_index_corrupted';
check table corrupted;
Table Op Msg_type Msg_text
test.corrupted check Warning InnoDB: Index a_key is marked as corrupted
test.corrupted check error Corrupt
SET debug_dbug = @save_dbug;
select * from corrupted;
ERROR HY000: Index corrupted is corrupted
alter table corrupted
drop key a_key,
add key a_key2 (a);
ERROR HY000: Index a_key is corrupted
alter table corrupted
drop key a_key;
select * from corrupted;
a
1
check table corrupted;
Table Op Msg_type Msg_text
test.corrupted check status OK
drop table corrupted;
create table t (
a int,
unique key a_key (a)
) engine=innodb stats_persistent=1;
SET @save_dbug = @@SESSION.debug_dbug;
SET debug_dbug = '+d,ib_rename_index_fail1';
alter table t
drop key a_key,
add unique key a_key2 (a),
algorithm=instant;
ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
SET debug_dbug = @save_dbug;
alter table t
drop key a_key,
add unique key `GEN_CLUST_INDEX` (a),
algorithm=instant;
ERROR 42000: Incorrect index name 'GEN_CLUST_INDEX'
show create table t;
Table Create Table
t CREATE TABLE `t` (
`a` int(11) DEFAULT NULL,
UNIQUE KEY `a_key` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 STATS_PERSISTENT=1
drop table t;
create table rename_column_and_index (
a int,
unique index a_key(a)
) engine=innodb;
insert into rename_column_and_index values (1), (3);
alter table rename_column_and_index
change a aa int,
drop key a_key,
add unique key aa_key(aa),
algorithm=instant;
show create table rename_column_and_index;
Table Create Table
rename_column_and_index CREATE TABLE `rename_column_and_index` (
`aa` int(11) DEFAULT NULL,
UNIQUE KEY `aa_key` (`aa`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
check table rename_column_and_index;
Table Op Msg_type Msg_text
test.rename_column_and_index check status OK
drop table rename_column_and_index;
......@@ -11,7 +11,7 @@ SET GLOBAL innodb_log_checkpoint_now=TRUE;
# Start an ALTER TABLE and stop it before renaming the files
SET DEBUG_SYNC='commit_cache_rebuild SIGNAL ready WAIT_FOR finish';
--send ALTER TABLE t1 ADD PRIMARY KEY(x)
--send ALTER TABLE t1 FORCE;
connect (con1,localhost,root,,);
......
......@@ -225,10 +225,7 @@ SET DEBUG_SYNC = 'row_log_apply_before SIGNAL c2e_created WAIT_FOR dml2_done';
# Ensure that the ALTER TABLE will be executed even with some concurrent DML.
SET lock_wait_timeout = 10;
--send
# FIXME: MDEV-13668
#ALTER TABLE t1 CHANGE c2 c22 INT, DROP INDEX c2d, ADD INDEX c2e(c22),
ALTER TABLE t1 DROP INDEX c2d, ADD INDEX c2e(c2),
ALGORITHM = INPLACE;
ALTER TABLE t1 CHANGE c2 c22 INT, DROP INDEX c2d, ADD INDEX c2e(c22, c3(10)), ALGORITHM = NOCOPY;
# Generate some log (delete-mark, delete-unmark, insert etc.)
# while the index creation is blocked. Some of this may run
......
--source include/have_innodb.inc
--source include/have_debug.inc
delimiter |;
create function get_index_id(tbl_id int, index_name char(100))
returns int
begin
declare res int;
select index_id into res from information_schema.innodb_sys_indexes where
name=index_name and table_id = tbl_id;
return res;
end|
delimiter ;|
create table t (
pk int primary key,
a int,
b int,
c int,
unique index a_key (a),
key c_key (c)
) engine=innodb stats_persistent=1;
insert into t values (1, 1, 1, 1);
set @table_id = (select table_id from information_schema.innodb_sys_tables where name='test/t');
set @a_key_id = get_index_id(@table_id, 'a_key');
set @c_key_id = get_index_id(@table_id, 'c_key');
set @primary_id = get_index_id(@table_id, 'primary');
select distinct(index_name) from mysql.innodb_index_stats where table_name = 't';
alter table t
drop index a_key,
add unique index a_key_strikes_back (a);
select distinct(index_name) from mysql.innodb_index_stats where table_name = 't';
check table t;
select @a_key_id = get_index_id(@table_id, 'a_key_strikes_back'),
@c_key_id = get_index_id(@table_id, 'c_key'),
@primary_id = get_index_id(@table_id, 'primary');
set @a_key_strikes_back_id = get_index_id(@table_id, 'a_key_strikes_back');
set @c_key_id = get_index_id(@table_id, 'c_key');
set @primary_id = get_index_id(@table_id, 'primary');
alter table t
drop index a_key_strikes_back,
add unique index a_key_returns (a),
drop primary key,
add primary key (pk),
add unique index b_key (b);
check table t;
select @a_key_strikes_back_id = get_index_id(@table_id, 'a_key_returns'),
@c_key_id = get_index_id(@table_id, 'c_key'),
@primary_id = get_index_id(@table_id, 'primary');
set @a_key_returns_id = get_index_id(@table_id, 'a_key_returns');
set @b_key_id = get_index_id(@table_id, 'b_key');
set @c_key_id = get_index_id(@table_id, 'c_key');
set @primary_id = get_index_id(@table_id, 'primary');
alter table t
drop key c_key,
add key c_key2 (c);
check table t;
select @a_key_returns_id = get_index_id(@table_id, 'a_key_returns'),
@b_key_id = get_index_id(@table_id, 'b_key'),
@c_key_id = get_index_id(@table_id, 'c_key2'),
@primary_id = get_index_id(@table_id, 'primary');
drop table t;
drop function get_index_id;
create table errors (
a int,
unique key a_key (a),
b int
) engine=innodb;
--error ER_CANT_DROP_FIELD_OR_KEY
alter table errors
drop key a_key,
drop key a_key,
add unique key a_key2 (a);
--error ER_CANT_DROP_FIELD_OR_KEY
alter table errors
drop key a_key,
drop key a_key2,
add unique key a_key2 (a);
--error ER_CANT_DROP_FIELD_OR_KEY
alter table errors
add key b_key (b),
drop key b_key,
add key bb_key (b);
--error ER_CANT_DROP_FIELD_OR_KEY
alter table errors
drop key a_key,
add key a_key2 (a),
drop key a_key,
add key a_key2 (a);
drop table errors;
--disable_query_log
call mtr.add_suppression("Flagged corruption of `a_key` in table `test`.`corrupted` in dict_set_index_corrupted");
--enable_query_log
create table corrupted (
a int,
key a_key (a)
) engine=innodb;
insert into corrupted values (1);
select * from corrupted;
SET @save_dbug = @@SESSION.debug_dbug;
SET debug_dbug = '+d,dict_set_index_corrupted';
check table corrupted;
SET debug_dbug = @save_dbug;
--error ER_INDEX_CORRUPT
select * from corrupted;
--error ER_INDEX_CORRUPT
alter table corrupted
drop key a_key,
add key a_key2 (a);
alter table corrupted
drop key a_key;
select * from corrupted;
check table corrupted;
drop table corrupted;
create table t (
a int,
unique key a_key (a)
) engine=innodb stats_persistent=1;
SET @save_dbug = @@SESSION.debug_dbug;
SET debug_dbug = '+d,ib_rename_index_fail1';
-- error ER_LOCK_DEADLOCK
alter table t
drop key a_key,
add unique key a_key2 (a),
algorithm=instant;
SET debug_dbug = @save_dbug;
--error ER_WRONG_NAME_FOR_INDEX
alter table t
drop key a_key,
add unique key `GEN_CLUST_INDEX` (a),
algorithm=instant;
show create table t;
drop table t;
create table rename_column_and_index (
a int,
unique index a_key(a)
) engine=innodb;
insert into rename_column_and_index values (1), (3);
alter table rename_column_and_index
change a aa int,
drop key a_key,
add unique key aa_key(aa),
algorithm=instant;
show create table rename_column_and_index;
check table rename_column_and_index;
drop table rename_column_and_index;
......@@ -4775,6 +4775,29 @@ handler::check_if_supported_inplace_alter(TABLE *altered_table,
DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
}
Alter_inplace_info::Alter_inplace_info(HA_CREATE_INFO *create_info_arg,
Alter_info *alter_info_arg,
KEY *key_info_arg, uint key_count_arg,
partition_info *modified_part_info_arg,
bool ignore_arg)
: create_info(create_info_arg),
alter_info(alter_info_arg),
key_info_buffer(key_info_arg),
key_count(key_count_arg),
index_drop_count(0),
index_drop_buffer(nullptr),
index_add_count(0),
index_add_buffer(nullptr),
rename_keys(current_thd->mem_root),
handler_ctx(nullptr),
group_commit_ctx(nullptr),
handler_flags(0),
modified_part_info(modified_part_info_arg),
ignore(ignore_arg),
online(false),
unsupported_reason(nullptr)
{}
void Alter_inplace_info::report_unsupported_error(const char *not_supported,
const char *try_instead) const
{
......
......@@ -43,6 +43,7 @@
#include <keycache.h>
#include <mysql/psi/mysql_table.h>
#include "sql_sequence.h"
#include "mem_root_array.h"
class Alter_info;
class Virtual_column_info;
......@@ -635,6 +636,8 @@ typedef ulonglong alter_table_operations;
#define ALTER_RECREATE (1ULL << 10)
// Set for CONVERT TO
#define ALTER_CONVERT_TO (1ULL << 11)
// Set for DROP ... ADD some_index
#define ALTER_RENAME_INDEX (1ULL << 12)
// Set for ADD FOREIGN KEY
#define ALTER_ADD_FOREIGN_KEY (1ULL << 21)
// Set for DROP FOREIGN KEY
......@@ -2367,6 +2370,29 @@ class Alter_inplace_info
*/
uint *index_add_buffer;
/**
Old and new index names. Used for index rename.
*/
struct Rename_key_pair
{
Rename_key_pair(const KEY *old_key, const KEY *new_key)
: old_key(old_key), new_key(new_key)
{
}
const KEY *old_key;
const KEY *new_key;
};
/**
Vector of key pairs from DROP/ADD index which can be renamed.
*/
typedef Mem_root_array<Rename_key_pair, true> Rename_keys_vector;
/**
A list of indexes which should be renamed.
Index definitions stays the same.
*/
Rename_keys_vector rename_keys;
/**
Context information to allow handlers to keep context between in-place
alter API calls.
......@@ -2428,23 +2454,7 @@ class Alter_inplace_info
Alter_info *alter_info_arg,
KEY *key_info_arg, uint key_count_arg,
partition_info *modified_part_info_arg,
bool ignore_arg)
: create_info(create_info_arg),
alter_info(alter_info_arg),
key_info_buffer(key_info_arg),
key_count(key_count_arg),
index_drop_count(0),
index_drop_buffer(NULL),
index_add_count(0),
index_add_buffer(NULL),
handler_ctx(NULL),
group_commit_ctx(NULL),
handler_flags(0),
modified_part_info(modified_part_info_arg),
ignore(ignore_arg),
online(false),
unsupported_reason(NULL)
{}
bool ignore_arg);
~Alter_inplace_info()
{
......
......@@ -87,9 +87,11 @@ class Mem_root_array
// Returns a pointer to the first element in the array.
Element_type *begin() { return &m_array[0]; }
const Element_type *begin() const { return &m_array[0]; }
// Returns a pointer to the past-the-end element in the array.
Element_type *end() { return &m_array[size()]; }
const Element_type *end() const { return &m_array[size()]; }
// Erases all of the elements.
void clear()
......@@ -226,6 +228,7 @@ class Mem_root_array
size_t element_size() const { return sizeof(Element_type); }
bool empty() const { return size() == 0; }
size_t size() const { return m_size; }
const MEM_ROOT *mem_root() const { return m_root; }
private:
MEM_ROOT *const m_root;
......
This diff is collapsed.
......@@ -3840,6 +3840,63 @@ dict_stats_rename_table(
return(ret);
}
/*********************************************************************//**
Renames an index in InnoDB persistent stats storage.
This function creates its own transaction and commits it.
@return DB_SUCCESS or error code. DB_STATS_DO_NOT_EXIST will be returned
if the persistent stats do not exist. */
dberr_t
dict_stats_rename_index(
/*====================*/
const dict_table_t* table, /*!< in: table whose index
is renamed */
const char* old_index_name, /*!< in: old index name */
const char* new_index_name) /*!< in: new index name */
{
rw_lock_x_lock(dict_operation_lock);
mutex_enter(&dict_sys->mutex);
if (!dict_stats_persistent_storage_check(true)) {
mutex_exit(&dict_sys->mutex);
rw_lock_x_unlock(dict_operation_lock);
return(DB_STATS_DO_NOT_EXIST);
}
char dbname_utf8[MAX_DB_UTF8_LEN];
char tablename_utf8[MAX_TABLE_UTF8_LEN];
dict_fs2utf8(table->name.m_name, dbname_utf8, sizeof(dbname_utf8),
tablename_utf8, sizeof(tablename_utf8));
pars_info_t* pinfo;
pinfo = pars_info_create();
pars_info_add_str_literal(pinfo, "dbname_utf8", dbname_utf8);
pars_info_add_str_literal(pinfo, "tablename_utf8", tablename_utf8);
pars_info_add_str_literal(pinfo, "new_index_name", new_index_name);
pars_info_add_str_literal(pinfo, "old_index_name", old_index_name);
dberr_t ret;
ret = dict_stats_exec_sql(
pinfo,
"PROCEDURE RENAME_INDEX_IN_INDEX_STATS () IS\n"
"BEGIN\n"
"UPDATE \"" INDEX_STATS_NAME "\" SET\n"
"index_name = :new_index_name\n"
"WHERE\n"
"database_name = :dbname_utf8 AND\n"
"table_name = :tablename_utf8 AND\n"
"index_name = :old_index_name;\n"
"END;\n", NULL);
mutex_exit(&dict_sys->mutex);
rw_lock_x_unlock(dict_operation_lock);
return(ret);
}
/* tests @{ */
#ifdef UNIV_ENABLE_UNIT_TEST_DICT_STATS
......
This diff is collapsed.
......@@ -187,6 +187,19 @@ dict_stats_rename_table(
char* errstr, /*!< out: error string if != DB_SUCCESS
is returned */
size_t errstr_sz); /*!< in: errstr size */
/*********************************************************************//**
Renames an index in InnoDB persistent stats storage.
This function creates its own transaction and commits it.
@return DB_SUCCESS or error code. DB_STATS_DO_NOT_EXIST will be returned
if the persistent stats do not exist. */
dberr_t
dict_stats_rename_index(
/*====================*/
const dict_table_t* table, /*!< in: table whose index
is renamed */
const char* old_index_name, /*!< in: old index name */
const char* new_index_name) /*!< in: new index name */
__attribute__((warn_unused_result));
/** Save an individual index's statistic into the persistent statistics
storage.
......
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