mirror of
https://github.com/MariaDB/server.git
synced 2026-01-06 05:22:24 +03:00
Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
corrupts a MERGE table
Bug 26867 - LOCK TABLES + REPAIR + merge table result in
memory/cpu hogging
Bug 26377 - Deadlock with MERGE and FLUSH TABLE
Bug 25038 - Waiting TRUNCATE
Bug 25700 - merge base tables get corrupted by
optimize/analyze/repair table
Bug 30275 - Merge tables: flush tables or unlock tables
causes server to crash
Bug 19627 - temporary merge table locking
Bug 27660 - Falcon: merge table possible
Bug 30273 - merge tables: Can't lock file (errno: 155)
The problems were:
Bug 26379 - Combination of FLUSH TABLE and REPAIR TABLE
corrupts a MERGE table
1. A thread trying to lock a MERGE table performs busy waiting while
REPAIR TABLE or a similar table administration task is ongoing on
one or more of its MyISAM tables.
2. A thread trying to lock a MERGE table performs busy waiting until all
threads that did REPAIR TABLE or similar table administration tasks
on one or more of its MyISAM tables in LOCK TABLES segments do UNLOCK
TABLES. The difference against problem #1 is that the busy waiting
takes place *after* the administration task. It is terminated by
UNLOCK TABLES only.
3. Two FLUSH TABLES within a LOCK TABLES segment can invalidate the
lock. This does *not* require a MERGE table. The first FLUSH TABLES
can be replaced by any statement that requires other threads to
reopen the table. In 5.0 and 5.1 a single FLUSH TABLES can provoke
the problem.
Bug 26867 - LOCK TABLES + REPAIR + merge table result in
memory/cpu hogging
Trying DML on a MERGE table, which has a child locked and
repaired by another thread, made an infinite loop in the server.
Bug 26377 - Deadlock with MERGE and FLUSH TABLE
Locking a MERGE table and its children in parent-child order
and flushing the child deadlocked the server.
Bug 25038 - Waiting TRUNCATE
Truncating a MERGE child, while the MERGE table was in use,
let the truncate fail instead of waiting for the table to
become free.
Bug 25700 - merge base tables get corrupted by
optimize/analyze/repair table
Repairing a child of an open MERGE table corrupted the child.
It was necessary to FLUSH the child first.
Bug 30275 - Merge tables: flush tables or unlock tables
causes server to crash
Flushing and optimizing locked MERGE children crashed the server.
Bug 19627 - temporary merge table locking
Use of a temporary MERGE table with non-temporary children
could corrupt the children.
Temporary tables are never locked. So we do now prohibit
non-temporary chidlren of a temporary MERGE table.
Bug 27660 - Falcon: merge table possible
It was possible to create a MERGE table with non-MyISAM children.
Bug 30273 - merge tables: Can't lock file (errno: 155)
This was a Windows-only bug. Table administration statements
sometimes failed with "Can't lock file (errno: 155)".
These bugs are fixed by a new implementation of MERGE table open.
When opening a MERGE table in open_tables() we do now add the
child tables to the list of tables to be opened by open_tables()
(the "query_list"). The children are not opened in the handler at
this stage.
After opening the parent, open_tables() opens each child from the
now extended query_list. When the last child is opened, we remove
the children from the query_list again and attach the children to
the parent. This behaves similar to the old open. However it does
not open the MyISAM tables directly, but grabs them from the already
open children.
When closing a MERGE table in close_thread_table() we detach the
children only. Closing of the children is done implicitly because
they are in thd->open_tables.
For more detail see the comment at the top of ha_myisammrg.cc.
Changed from open_ltable() to open_and_lock_tables() in all places
that can be relevant for MERGE tables. The latter can handle tables
added to the list on the fly. When open_ltable() was used in a loop
over a list of tables, the list must be temporarily terminated
after every table for open_and_lock_tables().
table_list->required_type is set to FRMTYPE_TABLE to avoid open of
special tables. Handling of derived tables is suppressed.
These details are handled by the new function
open_n_lock_single_table(), which has nearly the same signature as
open_ltable() and can replace it in most cases.
In reopen_tables() some of the tables open by a thread can be
closed and reopened. When a MERGE child is affected, the parent
must be closed and reopened too. Closing of the parent is forced
before the first child is closed. Reopen happens in the order of
thd->open_tables. MERGE parents do not attach their children
automatically at open. This is done after all tables are reopened.
So all children are open when attaching them.
Special lock handling like mysql_lock_abort() or mysql_lock_remove()
needs to be suppressed for MERGE children or forwarded to the parent.
This depends on the situation. In loops over all open tables one
suppresses child lock handling. When a single table is touched,
forwarding is done.
Behavioral changes:
===================
This patch changes the behavior of temporary MERGE tables.
Temporary MERGE must have temporary children.
The old behavior was wrong. A temporary table is not locked. Hence
even non-temporary children were not locked. See
Bug 19627 - temporary merge table locking.
You cannot change the union list of a non-temporary MERGE table
when LOCK TABLES is in effect. The following does *not* work:
CREATE TABLE m1 ... ENGINE=MRG_MYISAM ...;
LOCK TABLES t1 WRITE, t2 WRITE, m1 WRITE;
ALTER TABLE m1 ... UNION=(t1,t2) ...;
However, you can do this with a temporary MERGE table.
You cannot create a MERGE table with CREATE ... SELECT, neither
as a temporary MERGE table, nor as a non-temporary MERGE table.
CREATE TABLE m1 ... ENGINE=MRG_MYISAM ... SELECT ...;
Gives error message: table is not BASE TABLE.
This commit is contained in:
@@ -1556,13 +1556,18 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
|
||||
/* Don't give warnings for not found errors, as we already generate notes */
|
||||
thd->no_warnings_for_error= 1;
|
||||
|
||||
/* Remove the tables from the HANDLER list, if they are in it. */
|
||||
mysql_ha_flush(thd, tables, MYSQL_HA_CLOSE_FINAL, 1);
|
||||
|
||||
for (table= tables; table; table= table->next_local)
|
||||
{
|
||||
char *db=table->db;
|
||||
handlerton *table_type;
|
||||
enum legacy_db_type frm_db_type;
|
||||
|
||||
mysql_ha_flush(thd, table, MYSQL_HA_CLOSE_FINAL, 1);
|
||||
DBUG_PRINT("table", ("table_l: '%s'.'%s' table: 0x%lx s: 0x%lx",
|
||||
table->db, table->table_name, (long) table->table,
|
||||
table->table ? (long) table->table->s : (long) -1));
|
||||
|
||||
error= drop_temporary_table(thd, table);
|
||||
|
||||
@@ -1690,6 +1695,8 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
|
||||
wrong_tables.append(',');
|
||||
wrong_tables.append(String(table->table_name,system_charset_info));
|
||||
}
|
||||
DBUG_PRINT("table", ("table: 0x%lx s: 0x%lx", (long) table->table,
|
||||
table->table ? (long) table->table->s : (long) -1));
|
||||
}
|
||||
/*
|
||||
It's safe to unlock LOCK_open: we have an exclusive lock
|
||||
@@ -3836,6 +3843,8 @@ static int prepare_for_restore(THD* thd, TABLE_LIST* table,
|
||||
DBUG_RETURN(send_check_errmsg(thd, table, "restore",
|
||||
"Failed to open partially restored table"));
|
||||
}
|
||||
/* A MERGE table must not come here. */
|
||||
DBUG_ASSERT(!table->table || !table->table->child_l);
|
||||
pthread_mutex_unlock(&LOCK_open);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
@@ -3878,6 +3887,10 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
|
||||
table= &tmp_table;
|
||||
pthread_mutex_unlock(&LOCK_open);
|
||||
}
|
||||
|
||||
/* A MERGE table must not come here. */
|
||||
DBUG_ASSERT(!table->child_l);
|
||||
|
||||
/*
|
||||
REPAIR TABLE ... USE_FRM for temporary tables makes little sense.
|
||||
*/
|
||||
@@ -4032,6 +4045,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
char* db = table->db;
|
||||
bool fatal_error=0;
|
||||
|
||||
DBUG_PRINT("admin", ("table: '%s'.'%s'", table->db, table->table_name));
|
||||
DBUG_PRINT("admin", ("extra_open_options: %u", extra_open_options));
|
||||
strxmov(table_name, db, ".", table->table_name, NullS);
|
||||
thd->open_options|= extra_open_options;
|
||||
table->lock_type= lock_type;
|
||||
@@ -4062,16 +4077,24 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
table->next_local= save_next_local;
|
||||
thd->open_options&= ~extra_open_options;
|
||||
}
|
||||
DBUG_PRINT("admin", ("table: 0x%lx", (long) table->table));
|
||||
|
||||
if (prepare_func)
|
||||
{
|
||||
DBUG_PRINT("admin", ("calling prepare_func"));
|
||||
switch ((*prepare_func)(thd, table, check_opt)) {
|
||||
case 1: // error, message written to net
|
||||
ha_autocommit_or_rollback(thd, 1);
|
||||
close_thread_tables(thd);
|
||||
DBUG_PRINT("admin", ("simple error, admin next table"));
|
||||
continue;
|
||||
case -1: // error, message could be written to net
|
||||
/* purecov: begin inspected */
|
||||
DBUG_PRINT("admin", ("severe error, stop"));
|
||||
goto err;
|
||||
/* purecov: end */
|
||||
default: // should be 0 otherwise
|
||||
DBUG_PRINT("admin", ("prepare_func succeeded"));
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -4086,6 +4109,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
*/
|
||||
if (!table->table)
|
||||
{
|
||||
DBUG_PRINT("admin", ("open table failed"));
|
||||
if (!thd->warn_list.elements)
|
||||
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR,
|
||||
ER_CHECK_NO_SUCH_TABLE, ER(ER_CHECK_NO_SUCH_TABLE));
|
||||
@@ -4100,14 +4124,17 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
|
||||
if (table->view)
|
||||
{
|
||||
DBUG_PRINT("admin", ("calling view_operator_func"));
|
||||
result_code= (*view_operator_func)(thd, table);
|
||||
goto send_result;
|
||||
}
|
||||
|
||||
if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify)
|
||||
{
|
||||
/* purecov: begin inspected */
|
||||
char buff[FN_REFLEN + MYSQL_ERRMSG_SIZE];
|
||||
uint length;
|
||||
DBUG_PRINT("admin", ("sending error message"));
|
||||
protocol->prepare_for_resend();
|
||||
protocol->store(table_name, system_charset_info);
|
||||
protocol->store(operator_name, system_charset_info);
|
||||
@@ -4122,11 +4149,13 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
if (protocol->write())
|
||||
goto err;
|
||||
continue;
|
||||
/* purecov: end */
|
||||
}
|
||||
|
||||
/* Close all instances of the table to allow repair to rename files */
|
||||
if (lock_type == TL_WRITE && table->table->s->version)
|
||||
{
|
||||
DBUG_PRINT("admin", ("removing table from cache"));
|
||||
pthread_mutex_lock(&LOCK_open);
|
||||
const char *old_message=thd->enter_cond(&COND_refresh, &LOCK_open,
|
||||
"Waiting to get writelock");
|
||||
@@ -4146,6 +4175,8 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
|
||||
if (table->table->s->crashed && operator_func == &handler::ha_check)
|
||||
{
|
||||
/* purecov: begin inspected */
|
||||
DBUG_PRINT("admin", ("sending crashed warning"));
|
||||
protocol->prepare_for_resend();
|
||||
protocol->store(table_name, system_charset_info);
|
||||
protocol->store(operator_name, system_charset_info);
|
||||
@@ -4154,6 +4185,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
system_charset_info);
|
||||
if (protocol->write())
|
||||
goto err;
|
||||
/* purecov: end */
|
||||
}
|
||||
|
||||
if (operator_func == &handler::ha_repair &&
|
||||
@@ -4164,6 +4196,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
HA_ADMIN_NEEDS_ALTER))
|
||||
{
|
||||
my_bool save_no_send_ok= thd->net.no_send_ok;
|
||||
DBUG_PRINT("admin", ("recreating table"));
|
||||
ha_autocommit_or_rollback(thd, 1);
|
||||
close_thread_tables(thd);
|
||||
tmp_disable_binlog(thd); // binlogging is done by caller if wanted
|
||||
@@ -4176,7 +4209,9 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
||||
|
||||
}
|
||||
|
||||
DBUG_PRINT("admin", ("calling operator_func '%s'", operator_name));
|
||||
result_code = (table->table->file->*operator_func)(thd, check_opt);
|
||||
DBUG_PRINT("admin", ("operator_func returned: %d", result_code));
|
||||
|
||||
send_result:
|
||||
|
||||
@@ -5834,10 +5869,25 @@ view_err:
|
||||
start_waiting_global_read_lock(thd);
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
if (!(table=open_ltable(thd, table_list, TL_WRITE_ALLOW_READ, 0)))
|
||||
|
||||
if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ)))
|
||||
DBUG_RETURN(TRUE);
|
||||
table->use_all_columns();
|
||||
|
||||
/*
|
||||
Prohibit changing of the UNION list of a non-temporary MERGE table
|
||||
under LOCK tables. It would be quite difficult to reuse a shrinked
|
||||
set of tables from the old table or to open a new TABLE object for
|
||||
an extended list and verify that they belong to locked tables.
|
||||
*/
|
||||
if (thd->locked_tables &&
|
||||
(create_info->used_fields & HA_CREATE_USED_UNION) &&
|
||||
(table->s->tmp_table == NO_TMP_TABLE))
|
||||
{
|
||||
my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
||||
/* Check that we are not trying to rename to an existing table */
|
||||
if (new_name)
|
||||
{
|
||||
@@ -6305,6 +6355,7 @@ view_err:
|
||||
goto err;
|
||||
|
||||
/* Open the table if we need to copy the data. */
|
||||
DBUG_PRINT("info", ("need_copy_table: %u", need_copy_table));
|
||||
if (need_copy_table != ALTER_TABLE_METADATA_ONLY)
|
||||
{
|
||||
if (table->s->tmp_table)
|
||||
@@ -6328,6 +6379,10 @@ view_err:
|
||||
}
|
||||
if (!new_table)
|
||||
goto err1;
|
||||
/*
|
||||
Note: In case of MERGE table, we do not attach children. We do not
|
||||
copy data for MERGE tables. Only the children have data.
|
||||
*/
|
||||
}
|
||||
|
||||
/* Copy the data if necessary. */
|
||||
@@ -6335,6 +6390,10 @@ view_err:
|
||||
thd->cuted_fields=0L;
|
||||
thd->proc_info="copy to tmp table";
|
||||
copied=deleted=0;
|
||||
/*
|
||||
We do not copy data for MERGE tables. Only the children have data.
|
||||
MERGE tables have HA_NO_COPY_ON_ALTER set.
|
||||
*/
|
||||
if (new_table && !(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER))
|
||||
{
|
||||
/* We don't want update TIMESTAMP fields during ALTER TABLE. */
|
||||
@@ -6472,7 +6531,10 @@ view_err:
|
||||
|
||||
if (new_table)
|
||||
{
|
||||
/* Close the intermediate table that will be the new table */
|
||||
/*
|
||||
Close the intermediate table that will be the new table.
|
||||
Note that MERGE tables do not have their children attached here.
|
||||
*/
|
||||
intern_close_table(new_table);
|
||||
my_free(new_table,MYF(0));
|
||||
}
|
||||
@@ -6565,6 +6627,7 @@ view_err:
|
||||
/*
|
||||
Now we have to inform handler that new .FRM file is in place.
|
||||
To do this we need to obtain a handler object for it.
|
||||
NO need to tamper with MERGE tables. The real open is done later.
|
||||
*/
|
||||
TABLE *t_table;
|
||||
if (new_name != table_name || new_db != db)
|
||||
@@ -6632,7 +6695,7 @@ view_err:
|
||||
/*
|
||||
For the alter table to be properly flushed to the logs, we
|
||||
have to open the new table. If not, we get a problem on server
|
||||
shutdown.
|
||||
shutdown. But we do not need to attach MERGE children.
|
||||
*/
|
||||
char path[FN_REFLEN];
|
||||
TABLE *t_table;
|
||||
@@ -6962,6 +7025,12 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list)
|
||||
Alter_info alter_info;
|
||||
|
||||
DBUG_ENTER("mysql_recreate_table");
|
||||
DBUG_ASSERT(!table_list->next_global);
|
||||
/*
|
||||
table_list->table has been closed and freed. Do not reference
|
||||
uninitialized data. open_tables() could fail.
|
||||
*/
|
||||
table_list->table= NULL;
|
||||
|
||||
bzero((char*) &create_info, sizeof(create_info));
|
||||
create_info.db_type= 0;
|
||||
@@ -6993,6 +7062,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
|
||||
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
/* Open one table after the other to keep lock time as short as possible. */
|
||||
for (table= tables; table; table= table->next_local)
|
||||
{
|
||||
char table_name[NAME_LEN*2+2];
|
||||
@@ -7000,7 +7070,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
|
||||
|
||||
strxmov(table_name, table->db ,".", table->table_name, NullS);
|
||||
|
||||
t= table->table= open_ltable(thd, table, TL_READ, 0);
|
||||
t= table->table= open_n_lock_single_table(thd, table, TL_READ);
|
||||
thd->clear_error(); // these errors shouldn't get client
|
||||
|
||||
protocol->prepare_for_resend();
|
||||
|
||||
Reference in New Issue
Block a user