Commit 86975e04 authored by Oleksandr Byelkin's avatar Oleksandr Byelkin

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.
parent ba4ed3ee
#
# 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;
#
# 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;
--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;
--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;
......@@ -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
......
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