Commit 65820439 authored by Igor Babaev's avatar Igor Babaev

Fixed bug mdev-3891.

If a query referenced some system statistical tables, but not all of them,
then executing an ANALYZE command simultaneously with this query could
lead to a deadlock.
The fix prohibited reading statistics from system statistical tables
for such queries.

Removed the function unlock_tables_n_open_system_tables_for_write()
as not used anymore.
Performed some minor refactoring of the code in sql_statistics.cc. 
parent 109c104d
...@@ -202,6 +202,21 @@ dbt3_s001 lineitem i_l_shipdate 1 2.6500 ...@@ -202,6 +202,21 @@ dbt3_s001 lineitem i_l_shipdate 1 2.6500
dbt3_s001 lineitem i_l_suppkey 1 600.5000 dbt3_s001 lineitem i_l_suppkey 1 600.5000
dbt3_s001 lineitem i_l_suppkey_partkey 1 30.0250 dbt3_s001 lineitem i_l_suppkey_partkey 1 30.0250
dbt3_s001 lineitem i_l_suppkey_partkey 2 8.5786 dbt3_s001 lineitem i_l_suppkey_partkey 2 8.5786
set @save_global_use_stat_tables=@@global.use_stat_tables;
set global use_stat_tables='preferably';
set debug_sync='RESET';
set debug_sync='statistics_update_start SIGNAL parker WAIT_FOR go1 EXECUTE 1';
set debug_sync='thr_multi_lock_after_thr_lock SIGNAL go2 EXECUTE 2';
use dbt3_s001;
analyze table lineitem persistent for all;
set debug_sync='open_and_process_table WAIT_FOR parker';
set debug_sync='statistics_read_start SIGNAL go1 WAIT_FOR go2';
use dbt3_s001;
select * from mysql.index_stats, lineitem where index_name= 'i_l_shipdate' and l_orderkey=1 and l_partkey=68;
db_name table_name index_name prefix_arity avg_frequency l_orderkey l_partkey l_suppkey l_linenumber l_quantity l_extendedprice l_discount l_tax l_returnflag l_linestatus l_shipDATE l_commitDATE l_receiptDATE l_shipinstruct l_shipmode l_comment
dbt3_s001 lineitem i_l_shipdate 1 2.6500 1 68 9 2 36 34850.16 0.09 0.06 N O 1996-04-12 1996-02-28 1996-04-20 TAKE BACK RETURN MAIL slyly bold pinto beans detect s
set debug_sync='RESET';
set global use_stat_tables=@save_global_use_stat_tables;
DROP DATABASE dbt3_s001; DROP DATABASE dbt3_s001;
use test; use test;
set use_stat_tables=@save_use_stat_tables; set use_stat_tables=@save_use_stat_tables;
...@@ -211,6 +211,21 @@ dbt3_s001 lineitem i_l_shipdate 1 2.6496 ...@@ -211,6 +211,21 @@ dbt3_s001 lineitem i_l_shipdate 1 2.6496
dbt3_s001 lineitem i_l_suppkey 1 600.4000 dbt3_s001 lineitem i_l_suppkey 1 600.4000
dbt3_s001 lineitem i_l_suppkey_partkey 1 30.0200 dbt3_s001 lineitem i_l_suppkey_partkey 1 30.0200
dbt3_s001 lineitem i_l_suppkey_partkey 2 8.5771 dbt3_s001 lineitem i_l_suppkey_partkey 2 8.5771
set @save_global_use_stat_tables=@@global.use_stat_tables;
set global use_stat_tables='preferably';
set debug_sync='RESET';
set debug_sync='statistics_update_start SIGNAL parker WAIT_FOR go1 EXECUTE 1';
set debug_sync='thr_multi_lock_after_thr_lock SIGNAL go2 EXECUTE 2';
use dbt3_s001;
analyze table lineitem persistent for all;
set debug_sync='open_and_process_table WAIT_FOR parker';
set debug_sync='statistics_read_start SIGNAL go1 WAIT_FOR go2';
use dbt3_s001;
select * from mysql.index_stats, lineitem where index_name= 'i_l_shipdate' and l_orderkey=1 and l_partkey=68;
db_name table_name index_name prefix_arity avg_frequency l_orderkey l_partkey l_suppkey l_linenumber l_quantity l_extendedprice l_discount l_tax l_returnflag l_linestatus l_shipDATE l_commitDATE l_receiptDATE l_shipinstruct l_shipmode l_comment
dbt3_s001 lineitem i_l_shipdate 1 2.6496 1 68 9 2 36 34850.16 0.09 0.06 N O 1996-04-12 1996-02-28 1996-04-20 TAKE BACK RETURN MAIL slyly bold pinto beans detect s
set debug_sync='RESET';
set global use_stat_tables=@save_global_use_stat_tables;
DROP DATABASE dbt3_s001; DROP DATABASE dbt3_s001;
use test; use test;
set use_stat_tables=@save_use_stat_tables; set use_stat_tables=@save_use_stat_tables;
......
...@@ -196,6 +196,48 @@ set debug_sync='RESET'; ...@@ -196,6 +196,48 @@ set debug_sync='RESET';
select * from mysql.index_stats where table_name='lineitem' select * from mysql.index_stats where table_name='lineitem'
order by index_name, prefix_arity; order by index_name, prefix_arity;
#
# Bug mdev-3891: deadlock for ANALYZE and SELECT over mysql.index_stats
#
set @save_global_use_stat_tables=@@global.use_stat_tables;
set global use_stat_tables='preferably';
set debug_sync='RESET';
connect (con1, localhost, root,,);
connect (con2, localhost, root,,);
connection con1;
set debug_sync='statistics_update_start SIGNAL parker WAIT_FOR go1 EXECUTE 1';
set debug_sync='thr_multi_lock_after_thr_lock SIGNAL go2 EXECUTE 2';
use dbt3_s001;
--send analyze table lineitem persistent for all
connection con2;
set debug_sync='open_and_process_table WAIT_FOR parker';
set debug_sync='statistics_read_start SIGNAL go1 WAIT_FOR go2';
use dbt3_s001;
--send select * from mysql.index_stats, lineitem where index_name= 'i_l_shipdate' and l_orderkey=1 and l_partkey=68
connection con1;
--disable_result_log
--disable_warnings
--reap
--enable_warnings
--enable_result_log
connection con2;
--disable_warnings
--reap
--enable_warnings
connection default;
disconnect con1;
disconnect con2;
set debug_sync='RESET';
set global use_stat_tables=@save_global_use_stat_tables;
DROP DATABASE dbt3_s001; DROP DATABASE dbt3_s001;
use test; use test;
......
...@@ -9604,6 +9604,12 @@ has_write_table_auto_increment_not_first_in_pk(TABLE_LIST *tables) ...@@ -9604,6 +9604,12 @@ has_write_table_auto_increment_not_first_in_pk(TABLE_LIST *tables)
must call close_system_tables() to close systems tables opened must call close_system_tables() to close systems tables opened
with this call. with this call.
NOTES
In some situations we use this function to open system tables for
writing. It happens, for examples, with statistical tables when
they are updated by an ANALYZE command. In these cases we should
guarantee that system tables will not be deadlocked.
RETURN RETURN
FALSE Success FALSE Success
TRUE Error TRUE Error
...@@ -9648,70 +9654,6 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, ...@@ -9648,70 +9654,6 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
} }
/*
Unlock opened tables and open and lock system tables for write.
SYNOPSIS
unlock_tables_n_open_system_tables_for_write()
thd Thread context.
table_list List of tables to open.
backup Pointer to Open_tables_state instance where
information about currently open tables will be
saved, and from which will be restored when we will
end work with system tables.
DESCRIPTION
The function first unlocks the opened tables, but do not close them.
Then it opens and locks for write the specified system tables.
NOTE
The system tables cannot be locked for write without unlocking
the current opened tables. Yet in some cases we still need valid TABLE
structures for these tables to be able to extract data that is to be
written into the system tables.
This function is used when updating the statistical tables.
RETURN
FALSE Success
TRUE Error
*/
bool
unlock_tables_n_open_system_tables_for_write(THD *thd,
TABLE_LIST *table_list,
Open_tables_backup *backup)
{
Query_tables_list query_tables_list_backup;
LEX *lex= thd->lex;
DBUG_ENTER("unlock_tables_n_open_system_tables_for_write");
lex->reset_n_backup_query_tables_list(&query_tables_list_backup);
thd->reset_n_backup_open_tables_state(backup);
if (open_and_lock_tables(thd, table_list, FALSE,
MYSQL_OPEN_IGNORE_FLUSH | MYSQL_LOCK_IGNORE_TIMEOUT))
{
lex->restore_backup_query_tables_list(&query_tables_list_backup);
goto error;
}
for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global)
{
DBUG_ASSERT(tables->table->s->table_category == TABLE_CATEGORY_SYSTEM);
tables->table->use_all_columns();
}
lex->restore_backup_query_tables_list(&query_tables_list_backup);
DBUG_RETURN(FALSE);
error:
close_system_tables(thd, backup);
DBUG_RETURN(TRUE);
}
/* /*
Close system tables, opened with open_system_tables_for_read(). Close system tables, opened with open_system_tables_for_read().
......
...@@ -275,9 +275,6 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b); ...@@ -275,9 +275,6 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b);
/* Functions to work with system tables. */ /* Functions to work with system tables. */
bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list, bool open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
Open_tables_backup *backup); Open_tables_backup *backup);
bool unlock_tables_n_open_system_tables_for_write(THD *thd,
TABLE_LIST *table_list,
Open_tables_backup *backup);
void close_system_tables(THD *thd, Open_tables_backup *backup); void close_system_tables(THD *thd, Open_tables_backup *backup);
void close_mysql_tables(THD *thd); void close_mysql_tables(THD *thd);
TABLE *open_system_table_for_update(THD *thd, TABLE_LIST *one_table); TABLE *open_system_table_for_update(THD *thd, TABLE_LIST *one_table);
......
...@@ -126,6 +126,38 @@ inline void init_table_list_for_single_stat_table(TABLE_LIST *tbl, ...@@ -126,6 +126,38 @@ inline void init_table_list_for_single_stat_table(TABLE_LIST *tbl,
} }
/**
@brief
Open all statistical tables and lock them
*/
static
inline int open_stat_tables(THD *thd, TABLE_LIST *tables,
Open_tables_backup *backup,
bool for_write)
{
init_table_list_for_stat_tables(tables, for_write);
init_mdl_requests(tables);
return open_system_tables_for_read(thd, tables, backup);
}
/**
@brief
Open a statistical table and lock it
*/
static
inline int open_single_stat_table(THD *thd, TABLE_LIST *table,
const LEX_STRING *stat_tab_name,
Open_tables_backup *backup,
bool for_write)
{
init_table_list_for_single_stat_table(table, stat_tab_name, for_write);
init_mdl_requests(table);
return open_system_tables_for_read(thd, table, backup);
}
/** /**
@details @details
If the value of the parameter is_safe is TRUE then the function If the value of the parameter is_safe is TRUE then the function
...@@ -1199,7 +1231,7 @@ public: ...@@ -1199,7 +1231,7 @@ public:
the number of distinct values for a column. The class employs the the number of distinct values for a column. The class employs the
Unique class for this purpose. Unique class for this purpose.
The class Count_distinct_field is used only by the function The class Count_distinct_field is used only by the function
collect_statistics_from_table to calculate the values for collect_statistics_for_table to calculate the values for
column avg_frequency of the statistical table column_stats. column avg_frequency of the statistical table column_stats.
*/ */
...@@ -2179,12 +2211,9 @@ int update_statistics_for_table(THD *thd, TABLE *table) ...@@ -2179,12 +2211,9 @@ int update_statistics_for_table(THD *thd, TABLE *table)
DBUG_ENTER("update_statistics_for_table"); DBUG_ENTER("update_statistics_for_table");
init_table_list_for_stat_tables(tables, TRUE); DEBUG_SYNC(thd, "statistics_update_start");
init_mdl_requests(tables);
if (unlock_tables_n_open_system_tables_for_write(thd, if (open_stat_tables(thd, tables, &open_tables_backup, TRUE))
tables,
&open_tables_backup))
{ {
thd->clear_error(); thd->clear_error();
DBUG_RETURN(rc); DBUG_RETURN(rc);
...@@ -2266,7 +2295,7 @@ int update_statistics_for_table(THD *thd, TABLE *table) ...@@ -2266,7 +2295,7 @@ int update_statistics_for_table(THD *thd, TABLE *table)
The parameter stat_tables should point to an array of TABLE_LIST The parameter stat_tables should point to an array of TABLE_LIST
objects for all statistical tables linked into a list. All statistical objects for all statistical tables linked into a list. All statistical
tables are supposed to be opened. tables are supposed to be opened.
The function is called by read_statistics_for_table_if_needed(). The function is called by read_statistics_for_tables_if_needed().
@retval @retval
0 If data has been successfully read for the table 0 If data has been successfully read for the table
...@@ -2415,6 +2444,21 @@ bool statistics_for_tables_is_needed(THD *thd, TABLE_LIST *tables) ...@@ -2415,6 +2444,21 @@ bool statistics_for_tables_is_needed(THD *thd, TABLE_LIST *tables)
return FALSE; return FALSE;
} }
/*
Do not read statistics for any query over non-user tables.
If the query references some statistical tables, but not all
of them, reading the statistics may lead to a deadlock
*/
for (TABLE_LIST *tl= tables; tl; tl= tl->next_global)
{
if (!tl->is_view_or_derived() && tl->table)
{
TABLE_SHARE *table_share= tl->table->s;
if (table_share && table_share->table_category != TABLE_CATEGORY_USER)
return FALSE;
}
}
for (TABLE_LIST *tl= tables; tl; tl= tl->next_global) for (TABLE_LIST *tl= tables; tl; tl= tl->next_global)
{ {
if (!tl->is_view_or_derived() && tl->table) if (!tl->is_view_or_derived() && tl->table)
...@@ -2460,12 +2504,12 @@ int read_statistics_for_tables_if_needed(THD *thd, TABLE_LIST *tables) ...@@ -2460,12 +2504,12 @@ int read_statistics_for_tables_if_needed(THD *thd, TABLE_LIST *tables)
DBUG_ENTER("read_statistics_for_table_if_needed"); DBUG_ENTER("read_statistics_for_table_if_needed");
DEBUG_SYNC(thd, "statistics_read_start");
if (!statistics_for_tables_is_needed(thd, tables)) if (!statistics_for_tables_is_needed(thd, tables))
DBUG_RETURN(0); DBUG_RETURN(0);
init_table_list_for_stat_tables(stat_tables, FALSE); if (open_stat_tables(thd, stat_tables, &open_tables_backup, FALSE))
init_mdl_requests(stat_tables);
if (open_system_tables_for_read(thd, stat_tables, &open_tables_backup))
{ {
thd->clear_error(); thd->clear_error();
DBUG_RETURN(1); DBUG_RETURN(1);
...@@ -2527,12 +2571,7 @@ int delete_statistics_for_table(THD *thd, LEX_STRING *db, LEX_STRING *tab) ...@@ -2527,12 +2571,7 @@ int delete_statistics_for_table(THD *thd, LEX_STRING *db, LEX_STRING *tab)
DBUG_ENTER("delete_statistics_for_table"); DBUG_ENTER("delete_statistics_for_table");
init_table_list_for_stat_tables(tables, TRUE); if (open_stat_tables(thd, tables, &open_tables_backup, TRUE))
init_mdl_requests(tables);
if (open_system_tables_for_read(thd,
tables,
&open_tables_backup))
{ {
thd->clear_error(); thd->clear_error();
DBUG_RETURN(rc); DBUG_RETURN(rc);
...@@ -2619,12 +2658,8 @@ int delete_statistics_for_column(THD *thd, TABLE *tab, Field *col) ...@@ -2619,12 +2658,8 @@ int delete_statistics_for_column(THD *thd, TABLE *tab, Field *col)
DBUG_ENTER("delete_statistics_for_column"); DBUG_ENTER("delete_statistics_for_column");
init_table_list_for_single_stat_table(&tables, &stat_table_name[1], TRUE); if (open_single_stat_table(thd, &tables, &stat_table_name[1],
init_mdl_requests(&tables); &open_tables_backup, TRUE))
if (open_system_tables_for_read(thd,
&tables,
&open_tables_backup))
{ {
thd->clear_error(); thd->clear_error();
DBUG_RETURN(rc); DBUG_RETURN(rc);
...@@ -2692,12 +2727,8 @@ int delete_statistics_for_index(THD *thd, TABLE *tab, KEY *key_info, ...@@ -2692,12 +2727,8 @@ int delete_statistics_for_index(THD *thd, TABLE *tab, KEY *key_info,
DBUG_ENTER("delete_statistics_for_index"); DBUG_ENTER("delete_statistics_for_index");
init_table_list_for_single_stat_table(&tables, &stat_table_name[2], TRUE); if (open_single_stat_table(thd, &tables, &stat_table_name[2],
init_mdl_requests(&tables); &open_tables_backup, TRUE))
if (open_system_tables_for_read(thd,
&tables,
&open_tables_backup))
{ {
thd->clear_error(); thd->clear_error();
DBUG_RETURN(rc); DBUG_RETURN(rc);
...@@ -2779,12 +2810,7 @@ int rename_table_in_stat_tables(THD *thd, LEX_STRING *db, LEX_STRING *tab, ...@@ -2779,12 +2810,7 @@ int rename_table_in_stat_tables(THD *thd, LEX_STRING *db, LEX_STRING *tab,
DBUG_ENTER("rename_table_in_stat_tables"); DBUG_ENTER("rename_table_in_stat_tables");
init_table_list_for_stat_tables(tables, TRUE); if (open_stat_tables(thd, tables, &open_tables_backup, TRUE))
init_mdl_requests(tables);
if (open_system_tables_for_read(thd,
tables,
&open_tables_backup))
{ {
thd->clear_error(); thd->clear_error();
DBUG_RETURN(rc); DBUG_RETURN(rc);
...@@ -2876,12 +2902,8 @@ int rename_column_in_stat_tables(THD *thd, TABLE *tab, Field *col, ...@@ -2876,12 +2902,8 @@ int rename_column_in_stat_tables(THD *thd, TABLE *tab, Field *col,
DBUG_ENTER("rename_column_in_stat_tables"); DBUG_ENTER("rename_column_in_stat_tables");
init_table_list_for_single_stat_table(&tables, &stat_table_name[1], TRUE); if (open_single_stat_table(thd, &tables, &stat_table_name[1],
init_mdl_requests(&tables); &open_tables_backup, TRUE))
if (open_system_tables_for_read(thd,
&tables,
&open_tables_backup))
{ {
thd->clear_error(); thd->clear_error();
DBUG_RETURN(rc); DBUG_RETURN(rc);
......
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