mirror of
https://github.com/MariaDB/server.git
synced 2025-08-01 03:47:19 +03:00
MDEV-7901: re-implement analyze table for low impact
Table before collecting engine independent statistics now is reopened in read mode, InnoDB allow write operations in this case.
This commit is contained in:
25
mysql-test/r/stat_tables_innodb_debug.result
Normal file
25
mysql-test/r/stat_tables_innodb_debug.result
Normal file
@ -0,0 +1,25 @@
|
||||
#
|
||||
# MDEV-7901: re-implement analyze table for low impact
|
||||
#
|
||||
create table t1 (a int, b int, c int, key IDX1(a), key IDX2(a,b)) engine=innodb;
|
||||
insert into t1 select seq/10, seq/2, seq from seq_0_to_9;
|
||||
SET DEBUG_SYNC='statistics_collection_start1 SIGNAL analyzing WAIT_FOR written';
|
||||
analyze table t1 persistent for all;
|
||||
connect con1, localhost, root,,;
|
||||
connection con1;
|
||||
SET DEBUG_SYNC= 'now WAIT_FOR analyzing';
|
||||
select count(*) from t1;
|
||||
count(*)
|
||||
10
|
||||
insert into t1 values (333,333,333);
|
||||
update t1 set a=1;
|
||||
SET DEBUG_SYNC= 'now SIGNAL written';
|
||||
connection default;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t1 analyze status Engine-independent statistics collected
|
||||
test.t1 analyze status OK
|
||||
select count(*) from t1;
|
||||
count(*)
|
||||
11
|
||||
set debug_sync='RESET';
|
||||
drop table t1;
|
24
mysql-test/r/stat_tables_myisam_debug.result
Normal file
24
mysql-test/r/stat_tables_myisam_debug.result
Normal file
@ -0,0 +1,24 @@
|
||||
#
|
||||
# MDEV-7901: re-implement analyze table for low impact
|
||||
#
|
||||
create table t1 (a int, b int, c int, key IDX1(a), key IDX2(a,b)) engine=myisam;
|
||||
insert into t1 select seq/10, seq/2, seq from seq_0_to_9;
|
||||
SET DEBUG_SYNC='statistics_collection_start1 SIGNAL analyzing WAIT_FOR written';
|
||||
analyze table t1 persistent for all;
|
||||
connect con1, localhost, root,,;
|
||||
connection con1;
|
||||
SET DEBUG_SYNC= 'now WAIT_FOR analyzing';
|
||||
select count(*) from t1;
|
||||
count(*)
|
||||
10
|
||||
insert into t1 values (333,333,333);
|
||||
SET DEBUG_SYNC= 'now SIGNAL written';
|
||||
connection default;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t1 analyze status Engine-independent statistics collected
|
||||
test.t1 analyze status Table is already up to date
|
||||
select count(*) from t1;
|
||||
count(*)
|
||||
11
|
||||
set debug_sync='RESET';
|
||||
drop table t1;
|
36
mysql-test/t/stat_tables_innodb_debug.test
Normal file
36
mysql-test/t/stat_tables_innodb_debug.test
Normal file
@ -0,0 +1,36 @@
|
||||
--source include/have_innodb.inc
|
||||
--source include/have_sequence.inc
|
||||
--source include/have_stat_tables.inc
|
||||
--source include/have_debug_sync.inc
|
||||
--source include/not_embedded.inc
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-7901: re-implement analyze table for low impact
|
||||
--echo #
|
||||
|
||||
create table t1 (a int, b int, c int, key IDX1(a), key IDX2(a,b)) engine=innodb;
|
||||
insert into t1 select seq/10, seq/2, seq from seq_0_to_9;
|
||||
|
||||
|
||||
SET DEBUG_SYNC='statistics_collection_start1 SIGNAL analyzing WAIT_FOR written';
|
||||
|
||||
send analyze table t1 persistent for all;
|
||||
connect (con1, localhost, root,,);
|
||||
--connection con1
|
||||
|
||||
SET DEBUG_SYNC= 'now WAIT_FOR analyzing';
|
||||
|
||||
select count(*) from t1;
|
||||
# insert will work even with MyISAM because it allow to append file
|
||||
insert into t1 values (333,333,333);
|
||||
# but update now hang for MyISAM bacause can't get lock (InnoDB/XtraDB works)
|
||||
update t1 set a=1;
|
||||
|
||||
SET DEBUG_SYNC= 'now SIGNAL written';
|
||||
|
||||
--connection default
|
||||
--reap
|
||||
select count(*) from t1;
|
||||
|
||||
set debug_sync='RESET';
|
||||
drop table t1;
|
33
mysql-test/t/stat_tables_myisam_debug.test
Normal file
33
mysql-test/t/stat_tables_myisam_debug.test
Normal file
@ -0,0 +1,33 @@
|
||||
--source include/have_sequence.inc
|
||||
--source include/have_stat_tables.inc
|
||||
--source include/have_debug_sync.inc
|
||||
--source include/not_embedded.inc
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-7901: re-implement analyze table for low impact
|
||||
--echo #
|
||||
|
||||
create table t1 (a int, b int, c int, key IDX1(a), key IDX2(a,b)) engine=myisam;
|
||||
insert into t1 select seq/10, seq/2, seq from seq_0_to_9;
|
||||
|
||||
|
||||
SET DEBUG_SYNC='statistics_collection_start1 SIGNAL analyzing WAIT_FOR written';
|
||||
|
||||
send analyze table t1 persistent for all;
|
||||
connect (con1, localhost, root,,);
|
||||
--connection con1
|
||||
|
||||
SET DEBUG_SYNC= 'now WAIT_FOR analyzing';
|
||||
|
||||
select count(*) from t1;
|
||||
# insert will work even with MyISAM because it allow to append file
|
||||
insert into t1 values (333,333,333);
|
||||
|
||||
SET DEBUG_SYNC= 'now SIGNAL written';
|
||||
|
||||
--connection default
|
||||
--reap
|
||||
select count(*) from t1;
|
||||
|
||||
set debug_sync='RESET';
|
||||
drop table t1;
|
299
sql/sql_admin.cc
299
sql/sql_admin.cc
@ -296,6 +296,97 @@ static inline bool table_not_corrupt_error(uint sql_errno)
|
||||
}
|
||||
|
||||
|
||||
static bool open_only_one_table(THD* thd, TABLE_LIST* table,
|
||||
bool repair_table_use_frm,
|
||||
bool is_view_operator_func)
|
||||
{
|
||||
LEX *lex= thd->lex;
|
||||
SELECT_LEX *select= &lex->select_lex;
|
||||
TABLE_LIST *save_next_global, *save_next_local;
|
||||
bool open_error;
|
||||
save_next_global= table->next_global;
|
||||
table->next_global= 0;
|
||||
save_next_local= table->next_local;
|
||||
table->next_local= 0;
|
||||
select->table_list.first= table;
|
||||
/*
|
||||
Time zone tables and SP tables can be add to lex->query_tables list,
|
||||
so it have to be prepared.
|
||||
TODO: Investigate if we can put extra tables into argument instead of
|
||||
using lex->query_tables
|
||||
*/
|
||||
lex->query_tables= table;
|
||||
lex->query_tables_last= &table->next_global;
|
||||
lex->query_tables_own_last= 0;
|
||||
|
||||
/*
|
||||
CHECK TABLE command is allowed for views as well. Check on alter flags
|
||||
to differentiate from ALTER TABLE...CHECK PARTITION on which view is not
|
||||
allowed.
|
||||
*/
|
||||
if (lex->alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION ||
|
||||
!is_view_operator_func)
|
||||
{
|
||||
table->required_type=FRMTYPE_TABLE;
|
||||
DBUG_ASSERT(!lex->only_view);
|
||||
}
|
||||
else if (lex->only_view)
|
||||
{
|
||||
table->required_type= FRMTYPE_VIEW;
|
||||
}
|
||||
else if (!lex->only_view && lex->sql_command == SQLCOM_REPAIR)
|
||||
{
|
||||
table->required_type= FRMTYPE_TABLE;
|
||||
}
|
||||
|
||||
if (lex->sql_command == SQLCOM_CHECK ||
|
||||
lex->sql_command == SQLCOM_REPAIR ||
|
||||
lex->sql_command == SQLCOM_ANALYZE ||
|
||||
lex->sql_command == SQLCOM_OPTIMIZE)
|
||||
thd->prepare_derived_at_open= TRUE;
|
||||
if (!thd->locked_tables_mode && repair_table_use_frm)
|
||||
{
|
||||
/*
|
||||
If we're not under LOCK TABLES and we're executing REPAIR TABLE
|
||||
USE_FRM, we need to ignore errors from open_and_lock_tables().
|
||||
REPAIR TABLE USE_FRM is a heavy weapon used when a table is
|
||||
critically damaged, so open_and_lock_tables() will most likely
|
||||
report errors. Those errors are not interesting for the user
|
||||
because it's already known that the table is badly damaged.
|
||||
*/
|
||||
|
||||
Diagnostics_area *da= thd->get_stmt_da();
|
||||
Warning_info tmp_wi(thd->query_id, false, true);
|
||||
|
||||
da->push_warning_info(&tmp_wi);
|
||||
|
||||
open_error= (thd->open_temporary_tables(table) ||
|
||||
open_and_lock_tables(thd, table, TRUE, 0));
|
||||
|
||||
da->pop_warning_info();
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
It's assumed that even if it is REPAIR TABLE USE_FRM, the table
|
||||
can be opened if we're under LOCK TABLES (otherwise LOCK TABLES
|
||||
would fail). Thus, the only errors we could have from
|
||||
open_and_lock_tables() are logical ones, like incorrect locking
|
||||
mode. It does make sense for the user to see such errors.
|
||||
*/
|
||||
|
||||
open_error= (thd->open_temporary_tables(table) ||
|
||||
open_and_lock_tables(thd, table, TRUE, 0));
|
||||
}
|
||||
thd->prepare_derived_at_open= FALSE;
|
||||
|
||||
table->next_global= save_next_global;
|
||||
table->next_local= save_next_local;
|
||||
|
||||
return open_error;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
RETURN VALUES
|
||||
FALSE Message sent to net (admin operation went ok)
|
||||
@ -317,7 +408,6 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
HA_CHECK_OPT *))
|
||||
{
|
||||
TABLE_LIST *table;
|
||||
SELECT_LEX *select= &thd->lex->select_lex;
|
||||
List<Item> field_list;
|
||||
Item *item;
|
||||
Protocol *protocol= thd->protocol;
|
||||
@ -392,85 +482,9 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
/* open only one table from local list of command */
|
||||
while (1)
|
||||
{
|
||||
TABLE_LIST *save_next_global, *save_next_local;
|
||||
save_next_global= table->next_global;
|
||||
table->next_global= 0;
|
||||
save_next_local= table->next_local;
|
||||
table->next_local= 0;
|
||||
select->table_list.first= table;
|
||||
/*
|
||||
Time zone tables and SP tables can be add to lex->query_tables list,
|
||||
so it have to be prepared.
|
||||
TODO: Investigate if we can put extra tables into argument instead of
|
||||
using lex->query_tables
|
||||
*/
|
||||
lex->query_tables= table;
|
||||
lex->query_tables_last= &table->next_global;
|
||||
lex->query_tables_own_last= 0;
|
||||
|
||||
/*
|
||||
CHECK TABLE command is allowed for views as well. Check on alter flags
|
||||
to differentiate from ALTER TABLE...CHECK PARTITION on which view is not
|
||||
allowed.
|
||||
*/
|
||||
if (lex->alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION ||
|
||||
view_operator_func == NULL)
|
||||
{
|
||||
table->required_type=FRMTYPE_TABLE;
|
||||
DBUG_ASSERT(!lex->only_view);
|
||||
}
|
||||
else if (lex->only_view)
|
||||
{
|
||||
table->required_type= FRMTYPE_VIEW;
|
||||
}
|
||||
else if (!lex->only_view && lex->sql_command == SQLCOM_REPAIR)
|
||||
{
|
||||
table->required_type= FRMTYPE_TABLE;
|
||||
}
|
||||
|
||||
if (lex->sql_command == SQLCOM_CHECK ||
|
||||
lex->sql_command == SQLCOM_REPAIR ||
|
||||
lex->sql_command == SQLCOM_ANALYZE ||
|
||||
lex->sql_command == SQLCOM_OPTIMIZE)
|
||||
thd->prepare_derived_at_open= TRUE;
|
||||
if (!thd->locked_tables_mode && repair_table_use_frm)
|
||||
{
|
||||
/*
|
||||
If we're not under LOCK TABLES and we're executing REPAIR TABLE
|
||||
USE_FRM, we need to ignore errors from open_and_lock_tables().
|
||||
REPAIR TABLE USE_FRM is a heavy weapon used when a table is
|
||||
critically damaged, so open_and_lock_tables() will most likely
|
||||
report errors. Those errors are not interesting for the user
|
||||
because it's already known that the table is badly damaged.
|
||||
*/
|
||||
|
||||
Diagnostics_area *da= thd->get_stmt_da();
|
||||
Warning_info tmp_wi(thd->query_id, false, true);
|
||||
|
||||
da->push_warning_info(&tmp_wi);
|
||||
|
||||
open_error= (thd->open_temporary_tables(table) ||
|
||||
open_and_lock_tables(thd, table, TRUE, 0));
|
||||
|
||||
da->pop_warning_info();
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
It's assumed that even if it is REPAIR TABLE USE_FRM, the table
|
||||
can be opened if we're under LOCK TABLES (otherwise LOCK TABLES
|
||||
would fail). Thus, the only errors we could have from
|
||||
open_and_lock_tables() are logical ones, like incorrect locking
|
||||
mode. It does make sense for the user to see such errors.
|
||||
*/
|
||||
|
||||
open_error= (thd->open_temporary_tables(table) ||
|
||||
open_and_lock_tables(thd, table, TRUE, 0));
|
||||
}
|
||||
thd->prepare_derived_at_open= FALSE;
|
||||
|
||||
table->next_global= save_next_global;
|
||||
table->next_local= save_next_local;
|
||||
open_error= open_only_one_table(thd, table,
|
||||
repair_table_use_frm,
|
||||
(view_operator_func != NULL));
|
||||
thd->open_options&= ~extra_open_options;
|
||||
|
||||
/*
|
||||
@ -711,7 +725,6 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
if (operator_func == &handler::ha_analyze)
|
||||
{
|
||||
TABLE *tab= table->table;
|
||||
Field **field_ptr= tab->field;
|
||||
|
||||
if (lex->with_persistent_for_clause &&
|
||||
tab->s->table_category != TABLE_CATEGORY_USER)
|
||||
@ -723,58 +736,6 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
(get_use_stat_tables_mode(thd) > NEVER ||
|
||||
lex->with_persistent_for_clause));
|
||||
|
||||
if (collect_eis)
|
||||
{
|
||||
if (!lex->column_list)
|
||||
{
|
||||
bitmap_clear_all(tab->read_set);
|
||||
for (uint fields= 0; *field_ptr; field_ptr++, fields++)
|
||||
{
|
||||
enum enum_field_types type= (*field_ptr)->type();
|
||||
if (type < MYSQL_TYPE_MEDIUM_BLOB ||
|
||||
type > MYSQL_TYPE_BLOB)
|
||||
bitmap_set_bit(tab->read_set, fields);
|
||||
else if (collect_eis)
|
||||
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
||||
ER_NO_EIS_FOR_FIELD,
|
||||
ER_THD(thd, ER_NO_EIS_FOR_FIELD),
|
||||
(*field_ptr)->field_name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int pos;
|
||||
LEX_STRING *column_name;
|
||||
List_iterator_fast<LEX_STRING> it(*lex->column_list);
|
||||
|
||||
bitmap_clear_all(tab->read_set);
|
||||
while ((column_name= it++))
|
||||
{
|
||||
if (tab->s->fieldnames.type_names == 0 ||
|
||||
(pos= find_type(&tab->s->fieldnames, column_name->str,
|
||||
column_name->length, 1)) <= 0)
|
||||
{
|
||||
compl_result_code= result_code= HA_ADMIN_INVALID;
|
||||
break;
|
||||
}
|
||||
pos--;
|
||||
enum enum_field_types type= tab->field[pos]->type();
|
||||
if (type < MYSQL_TYPE_MEDIUM_BLOB ||
|
||||
type > MYSQL_TYPE_BLOB)
|
||||
bitmap_set_bit(tab->read_set, pos);
|
||||
else if (collect_eis)
|
||||
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
||||
ER_NO_EIS_FOR_FIELD,
|
||||
ER_THD(thd, ER_NO_EIS_FOR_FIELD),
|
||||
column_name->str);
|
||||
}
|
||||
tab->file->column_bitmaps_signal();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DBUG_ASSERT(!lex->column_list);
|
||||
}
|
||||
|
||||
if (!lex->index_list)
|
||||
{
|
||||
@ -812,7 +773,77 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
|
||||
if (compl_result_code == HA_ADMIN_OK && collect_eis)
|
||||
{
|
||||
if (!(compl_result_code=
|
||||
/*
|
||||
Here we close and reopen table in read mode because operation of
|
||||
collecting statistics is long and it will be better do not block
|
||||
the table completely.
|
||||
InnoDB/XtraDB will allow read/write and MyISAM read/insert.
|
||||
*/
|
||||
trans_commit_stmt(thd);
|
||||
trans_commit(thd);
|
||||
thd->open_options|= extra_open_options;
|
||||
close_thread_tables(thd);
|
||||
table->table= NULL;
|
||||
thd->mdl_context.release_transactional_locks();
|
||||
table->mdl_request.init(MDL_key::TABLE, table->db, table->table_name,
|
||||
MDL_SHARED_NO_READ_WRITE, MDL_TRANSACTION);
|
||||
table->mdl_request.set_type(MDL_SHARED_READ);
|
||||
|
||||
table->lock_type= TL_READ;
|
||||
DBUG_ASSERT(view_operator_func == NULL);
|
||||
open_error= open_only_one_table(thd, table,
|
||||
repair_table_use_frm, FALSE);
|
||||
thd->open_options&= ~extra_open_options;
|
||||
|
||||
TABLE *tab= table->table;
|
||||
Field **field_ptr= tab->field;
|
||||
if (!lex->column_list)
|
||||
{
|
||||
bitmap_clear_all(tab->read_set);
|
||||
for (uint fields= 0; *field_ptr; field_ptr++, fields++)
|
||||
{
|
||||
enum enum_field_types type= (*field_ptr)->type();
|
||||
if (type < MYSQL_TYPE_MEDIUM_BLOB ||
|
||||
type > MYSQL_TYPE_BLOB)
|
||||
bitmap_set_bit(tab->read_set, fields);
|
||||
else
|
||||
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
||||
ER_NO_EIS_FOR_FIELD,
|
||||
ER_THD(thd, ER_NO_EIS_FOR_FIELD),
|
||||
(*field_ptr)->field_name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int pos;
|
||||
LEX_STRING *column_name;
|
||||
List_iterator_fast<LEX_STRING> it(*lex->column_list);
|
||||
|
||||
bitmap_clear_all(tab->read_set);
|
||||
while ((column_name= it++))
|
||||
{
|
||||
if (tab->s->fieldnames.type_names == 0 ||
|
||||
(pos= find_type(&tab->s->fieldnames, column_name->str,
|
||||
column_name->length, 1)) <= 0)
|
||||
{
|
||||
compl_result_code= result_code= HA_ADMIN_INVALID;
|
||||
break;
|
||||
}
|
||||
pos--;
|
||||
enum enum_field_types type= tab->field[pos]->type();
|
||||
if (type < MYSQL_TYPE_MEDIUM_BLOB ||
|
||||
type > MYSQL_TYPE_BLOB)
|
||||
bitmap_set_bit(tab->read_set, pos);
|
||||
else
|
||||
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
||||
ER_NO_EIS_FOR_FIELD,
|
||||
ER_THD(thd, ER_NO_EIS_FOR_FIELD),
|
||||
column_name->str);
|
||||
}
|
||||
tab->file->column_bitmaps_signal();
|
||||
}
|
||||
if (!open_error &&
|
||||
!(compl_result_code=
|
||||
alloc_statistics_for_table(thd, table->table)) &&
|
||||
!(compl_result_code=
|
||||
collect_statistics_for_table(thd, table->table)))
|
||||
|
@ -15502,6 +15502,7 @@ ha_innobase::store_lock(
|
||||
/* Use consistent read for checksum table */
|
||||
|
||||
if (sql_command == SQLCOM_CHECKSUM
|
||||
|| (sql_command == SQLCOM_ANALYZE && lock_type == TL_READ)
|
||||
|| ((srv_locks_unsafe_for_binlog
|
||||
|| trx->isolation_level <= TRX_ISO_READ_COMMITTED)
|
||||
&& trx->isolation_level != TRX_ISO_SERIALIZABLE
|
||||
|
@ -16247,7 +16247,7 @@ ha_innobase::store_lock(
|
||||
/* Use consistent read for checksum table */
|
||||
|
||||
if (sql_command == SQLCOM_CHECKSUM
|
||||
|| sql_command == SQLCOM_CHECKSUM
|
||||
|| (sql_command == SQLCOM_ANALYZE && lock_type == TL_READ)
|
||||
|| ((srv_locks_unsafe_for_binlog
|
||||
|| trx->isolation_level <= TRX_ISO_READ_COMMITTED)
|
||||
&& trx->isolation_level != TRX_ISO_SERIALIZABLE
|
||||
|
Reference in New Issue
Block a user