mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
Bug#10224 - ANALYZE TABLE crashing with simultaneous CREATE ... SELECT statement.
Manual merge from 4.1.
This commit is contained in:
@ -579,3 +579,21 @@ select * from t2;
|
|||||||
b
|
b
|
||||||
1
|
1
|
||||||
drop table t1,t2;
|
drop table t1,t2;
|
||||||
|
use test;
|
||||||
|
create table t1 (a int);
|
||||||
|
create table t1 select * from t1;
|
||||||
|
ERROR HY000: You can't specify target table 't1' for update in FROM clause
|
||||||
|
create table t2 union = (t1) select * from t1;
|
||||||
|
ERROR HY000: You can't specify target table 't1' for update in FROM clause
|
||||||
|
flush tables with read lock;
|
||||||
|
unlock tables;
|
||||||
|
drop table t1;
|
||||||
|
create table t1(column.name int);
|
||||||
|
ERROR 42000: Incorrect table name 'column'
|
||||||
|
create table t1(test.column.name int);
|
||||||
|
ERROR 42000: Incorrect table name 'column'
|
||||||
|
create table t1(xyz.t1.name int);
|
||||||
|
ERROR 42000: Incorrect database name 'xyz'
|
||||||
|
create table t1(t1.name int);
|
||||||
|
create table t2(test.t2.name int);
|
||||||
|
drop table t1,t2;
|
||||||
|
@ -471,3 +471,33 @@ insert into t2 values ();
|
|||||||
select * from t1;
|
select * from t1;
|
||||||
select * from t2;
|
select * from t2;
|
||||||
drop table t1,t2;
|
drop table t1,t2;
|
||||||
|
|
||||||
|
#
|
||||||
|
# Bug#10224 - ANALYZE TABLE crashing with simultaneous
|
||||||
|
# CREATE ... SELECT statement.
|
||||||
|
# This tests two additional possible errors and a hang if
|
||||||
|
# an improper fix is present.
|
||||||
|
#
|
||||||
|
connection default;
|
||||||
|
use test;
|
||||||
|
create table t1 (a int);
|
||||||
|
--error 1093
|
||||||
|
create table t1 select * from t1;
|
||||||
|
--error 1093
|
||||||
|
create table t2 union = (t1) select * from t1;
|
||||||
|
flush tables with read lock;
|
||||||
|
unlock tables;
|
||||||
|
drop table t1;
|
||||||
|
|
||||||
|
#
|
||||||
|
# Bug#10413: Invalid column name is not rejected
|
||||||
|
#
|
||||||
|
--error 1103
|
||||||
|
create table t1(column.name int);
|
||||||
|
--error 1103
|
||||||
|
create table t1(test.column.name int);
|
||||||
|
--error 1102
|
||||||
|
create table t1(xyz.t1.name int);
|
||||||
|
create table t1(t1.name int);
|
||||||
|
create table t2(test.t2.name int);
|
||||||
|
drop table t1,t2;
|
||||||
|
70
sql/lock.cc
70
sql/lock.cc
@ -82,8 +82,24 @@ static int unlock_external(THD *thd, TABLE **table,uint count);
|
|||||||
static void print_lock_error(int error, const char *);
|
static void print_lock_error(int error, const char *);
|
||||||
|
|
||||||
|
|
||||||
MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
|
/*
|
||||||
bool ignore_global_read_lock)
|
Lock tables.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
mysql_lock_tables()
|
||||||
|
thd The current thread.
|
||||||
|
tables An array of pointers to the tables to lock.
|
||||||
|
count The number of tables to lock.
|
||||||
|
flags Options:
|
||||||
|
MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK Ignore a global read lock
|
||||||
|
MYSQL_LOCK_IGNORE_FLUSH Ignore a flush tables.
|
||||||
|
|
||||||
|
RETURN
|
||||||
|
A lock structure pointer on success.
|
||||||
|
NULL on error.
|
||||||
|
*/
|
||||||
|
|
||||||
|
MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags)
|
||||||
{
|
{
|
||||||
MYSQL_LOCK *sql_lock;
|
MYSQL_LOCK *sql_lock;
|
||||||
TABLE *write_lock_used;
|
TABLE *write_lock_used;
|
||||||
@ -94,7 +110,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
|
|||||||
if (!(sql_lock = get_lock_data(thd,tables,count, 0,&write_lock_used)))
|
if (!(sql_lock = get_lock_data(thd,tables,count, 0,&write_lock_used)))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (global_read_lock && write_lock_used && ! ignore_global_read_lock)
|
if (global_read_lock && write_lock_used &&
|
||||||
|
! (flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
Someone has issued LOCK ALL TABLES FOR READ and we want a write lock
|
Someone has issued LOCK ALL TABLES FOR READ and we want a write lock
|
||||||
@ -128,7 +145,7 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count,
|
|||||||
thd->some_tables_deleted=1; // Try again
|
thd->some_tables_deleted=1; // Try again
|
||||||
sql_lock->lock_count=0; // Locks are alread freed
|
sql_lock->lock_count=0; // Locks are alread freed
|
||||||
}
|
}
|
||||||
else if (!thd->some_tables_deleted)
|
else if (!thd->some_tables_deleted || (flags & MYSQL_LOCK_IGNORE_FLUSH))
|
||||||
{
|
{
|
||||||
thd->locked=0;
|
thd->locked=0;
|
||||||
break;
|
break;
|
||||||
@ -951,48 +968,3 @@ bool make_global_read_lock_block_commit(THD *thd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set protection against global read lock.
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
set_protect_against_global_read_lock()
|
|
||||||
void
|
|
||||||
|
|
||||||
RETURN
|
|
||||||
FALSE OK, no global read lock exists.
|
|
||||||
TRUE Error, global read lock exists already.
|
|
||||||
*/
|
|
||||||
|
|
||||||
bool set_protect_against_global_read_lock(void)
|
|
||||||
{
|
|
||||||
bool global_read_lock_exists;
|
|
||||||
|
|
||||||
pthread_mutex_lock(&LOCK_open);
|
|
||||||
if (! (global_read_lock_exists= test(global_read_lock)))
|
|
||||||
protect_against_global_read_lock++;
|
|
||||||
pthread_mutex_unlock(&LOCK_open);
|
|
||||||
return global_read_lock_exists;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Unset protection against global read lock.
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
unset_protect_against_global_read_lock()
|
|
||||||
void
|
|
||||||
|
|
||||||
RETURN
|
|
||||||
void
|
|
||||||
*/
|
|
||||||
|
|
||||||
void unset_protect_against_global_read_lock(void)
|
|
||||||
{
|
|
||||||
pthread_mutex_lock(&LOCK_open);
|
|
||||||
protect_against_global_read_lock--;
|
|
||||||
pthread_mutex_unlock(&LOCK_open);
|
|
||||||
pthread_cond_broadcast(&COND_refresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1166,8 +1166,11 @@ extern pthread_t signal_thread;
|
|||||||
extern struct st_VioSSLAcceptorFd * ssl_acceptor_fd;
|
extern struct st_VioSSLAcceptorFd * ssl_acceptor_fd;
|
||||||
#endif /* HAVE_OPENSSL */
|
#endif /* HAVE_OPENSSL */
|
||||||
|
|
||||||
MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count,
|
MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, uint flags);
|
||||||
bool ignore_global_read_lock= FALSE);
|
/* mysql_lock_tables() flags bits */
|
||||||
|
#define MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK 0x0001
|
||||||
|
#define MYSQL_LOCK_IGNORE_FLUSH 0x0002
|
||||||
|
|
||||||
void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock);
|
void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock);
|
||||||
void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock);
|
void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock);
|
||||||
void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count);
|
void mysql_unlock_some_tables(THD *thd, TABLE **table,uint count);
|
||||||
|
@ -1384,7 +1384,7 @@ bool reopen_tables(THD *thd,bool get_locks,bool in_refresh)
|
|||||||
MYSQL_LOCK *lock;
|
MYSQL_LOCK *lock;
|
||||||
/* We should always get these locks */
|
/* We should always get these locks */
|
||||||
thd->some_tables_deleted=0;
|
thd->some_tables_deleted=0;
|
||||||
if ((lock=mysql_lock_tables(thd,tables,(uint) (tables_ptr-tables))))
|
if ((lock= mysql_lock_tables(thd, tables, (uint) (tables_ptr-tables), 0)))
|
||||||
{
|
{
|
||||||
thd->locked_tables=mysql_lock_merge(thd->locked_tables,lock);
|
thd->locked_tables=mysql_lock_merge(thd->locked_tables,lock);
|
||||||
}
|
}
|
||||||
@ -2022,7 +2022,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type)
|
|||||||
{
|
{
|
||||||
DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
|
DBUG_ASSERT(thd->lock == 0); // You must lock everything at once
|
||||||
if ((table->reginfo.lock_type= lock_type) != TL_UNLOCK)
|
if ((table->reginfo.lock_type= lock_type) != TL_UNLOCK)
|
||||||
if (!(thd->lock=mysql_lock_tables(thd,&table_list->table,1)))
|
if (! (thd->lock= mysql_lock_tables(thd, &table_list->table, 1, 0)))
|
||||||
table= 0;
|
table= 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2237,7 +2237,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count)
|
|||||||
thd->options|= OPTION_TABLE_LOCK;
|
thd->options|= OPTION_TABLE_LOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(thd->lock=mysql_lock_tables(thd,start, (uint) (ptr - start))))
|
if (! (thd->lock= mysql_lock_tables(thd, start, (uint) (ptr - start), 0)))
|
||||||
{
|
{
|
||||||
if (thd->lex->requires_prelocking())
|
if (thd->lex->requires_prelocking())
|
||||||
{
|
{
|
||||||
|
@ -433,7 +433,7 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables,
|
|||||||
protocol->send_fields(&list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF);
|
protocol->send_fields(&list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF);
|
||||||
|
|
||||||
HANDLER_TABLES_HACK(thd);
|
HANDLER_TABLES_HACK(thd);
|
||||||
lock= mysql_lock_tables(thd, &tables->table, 1);
|
lock= mysql_lock_tables(thd, &tables->table, 1, 0);
|
||||||
HANDLER_TABLES_HACK(thd);
|
HANDLER_TABLES_HACK(thd);
|
||||||
|
|
||||||
if (!lock)
|
if (!lock)
|
||||||
|
@ -1211,10 +1211,13 @@ static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list)
|
|||||||
Avoid that a global read lock steps in while we are creating the
|
Avoid that a global read lock steps in while we are creating the
|
||||||
new thread. It would block trying to open the table. Hence, the
|
new thread. It would block trying to open the table. Hence, the
|
||||||
DI thread and this thread would wait until after the global
|
DI thread and this thread would wait until after the global
|
||||||
readlock is gone. If the read lock exists already, we leave with
|
readlock is gone. Since the insert thread needs to wait for a
|
||||||
no table and then switch to non-delayed insert.
|
global read lock anyway, we do it right now. Note that
|
||||||
|
wait_if_global_read_lock() sets a protection against a new
|
||||||
|
global read lock when it succeeds. This needs to be released by
|
||||||
|
start_waiting_global_read_lock().
|
||||||
*/
|
*/
|
||||||
if (set_protect_against_global_read_lock())
|
if (wait_if_global_read_lock(thd, 0, 1))
|
||||||
goto err;
|
goto err;
|
||||||
if (!(tmp=new delayed_insert()))
|
if (!(tmp=new delayed_insert()))
|
||||||
{
|
{
|
||||||
@ -1256,7 +1259,11 @@ static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list)
|
|||||||
pthread_cond_wait(&tmp->cond_client,&tmp->mutex);
|
pthread_cond_wait(&tmp->cond_client,&tmp->mutex);
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&tmp->mutex);
|
pthread_mutex_unlock(&tmp->mutex);
|
||||||
unset_protect_against_global_read_lock();
|
/*
|
||||||
|
Release the protection against the global read lock and wake
|
||||||
|
everyone, who might want to set a global read lock.
|
||||||
|
*/
|
||||||
|
start_waiting_global_read_lock(thd);
|
||||||
thd->proc_info="got old table";
|
thd->proc_info="got old table";
|
||||||
if (tmp->thd.killed)
|
if (tmp->thd.killed)
|
||||||
{
|
{
|
||||||
@ -1292,7 +1299,11 @@ static TABLE *delayed_get_table(THD *thd,TABLE_LIST *table_list)
|
|||||||
|
|
||||||
err1:
|
err1:
|
||||||
thd->fatal_error();
|
thd->fatal_error();
|
||||||
unset_protect_against_global_read_lock();
|
/*
|
||||||
|
Release the protection against the global read lock and wake
|
||||||
|
everyone, who might want to set a global read lock.
|
||||||
|
*/
|
||||||
|
start_waiting_global_read_lock(thd);
|
||||||
err:
|
err:
|
||||||
pthread_mutex_unlock(&LOCK_delayed_create);
|
pthread_mutex_unlock(&LOCK_delayed_create);
|
||||||
DBUG_RETURN(0); // Continue with normal insert
|
DBUG_RETURN(0); // Continue with normal insert
|
||||||
@ -1650,7 +1661,8 @@ extern "C" pthread_handler_decl(handle_delayed_insert,arg)
|
|||||||
handler will close the table and finish when the outstanding
|
handler will close the table and finish when the outstanding
|
||||||
inserts are done.
|
inserts are done.
|
||||||
*/
|
*/
|
||||||
if (! (thd->lock= mysql_lock_tables(thd, &di->table, 1, TRUE)))
|
if (! (thd->lock= mysql_lock_tables(thd, &di->table, 1,
|
||||||
|
MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK)))
|
||||||
{
|
{
|
||||||
/* Fatal error */
|
/* Fatal error */
|
||||||
di->dead= 1;
|
di->dead= 1;
|
||||||
|
@ -2773,6 +2773,24 @@ mysql_execute_command(THD *thd)
|
|||||||
lex->create_info.default_table_charset= lex->create_info.table_charset;
|
lex->create_info.default_table_charset= lex->create_info.table_charset;
|
||||||
lex->create_info.table_charset= 0;
|
lex->create_info.table_charset= 0;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
The create-select command will open and read-lock the select table
|
||||||
|
and then create, open and write-lock the new table. If a global
|
||||||
|
read lock steps in, we get a deadlock. The write lock waits for
|
||||||
|
the global read lock, while the global read lock waits for the
|
||||||
|
select table to be closed. So we wait until the global readlock is
|
||||||
|
gone before starting both steps. Note that
|
||||||
|
wait_if_global_read_lock() sets a protection against a new global
|
||||||
|
read lock when it succeeds. This needs to be released by
|
||||||
|
start_waiting_global_read_lock(). We protect the normal CREATE
|
||||||
|
TABLE in the same way. That way we avoid that a new table is
|
||||||
|
created during a gobal read lock.
|
||||||
|
*/
|
||||||
|
if (wait_if_global_read_lock(thd, 0, 1))
|
||||||
|
{
|
||||||
|
res= -1;
|
||||||
|
goto unsent_create_error;
|
||||||
|
}
|
||||||
if (select_lex->item_list.elements) // With select
|
if (select_lex->item_list.elements) // With select
|
||||||
{
|
{
|
||||||
select_result *result;
|
select_result *result;
|
||||||
@ -2846,6 +2864,11 @@ mysql_execute_command(THD *thd)
|
|||||||
if (!res)
|
if (!res)
|
||||||
send_ok(thd);
|
send_ok(thd);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
Release the protection against the global read lock and wake
|
||||||
|
everyone, who might want to set a global read lock.
|
||||||
|
*/
|
||||||
|
start_waiting_global_read_lock(thd);
|
||||||
lex->link_first_table_back(create_table, link_to_local);
|
lex->link_first_table_back(create_table, link_to_local);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1756,7 +1756,7 @@ TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
|
|||||||
}
|
}
|
||||||
|
|
||||||
table->reginfo.lock_type=TL_WRITE;
|
table->reginfo.lock_type=TL_WRITE;
|
||||||
if (!((*lock)= mysql_lock_tables(thd, &table,1)))
|
if (! ((*lock)= mysql_lock_tables(thd, &table, 1, MYSQL_LOCK_IGNORE_FLUSH)))
|
||||||
{
|
{
|
||||||
VOID(pthread_mutex_lock(&LOCK_open));
|
VOID(pthread_mutex_lock(&LOCK_open));
|
||||||
hash_delete(&open_cache,(byte*) table);
|
hash_delete(&open_cache,(byte*) table);
|
||||||
|
@ -7078,7 +7078,31 @@ simple_ident_q:
|
|||||||
|
|
||||||
field_ident:
|
field_ident:
|
||||||
ident { $$=$1;}
|
ident { $$=$1;}
|
||||||
| ident '.' ident { $$=$3;} /* Skip schema name in create*/
|
| ident '.' ident '.' ident
|
||||||
|
{
|
||||||
|
TABLE_LIST *table= (TABLE_LIST*) Select->table_list.first;
|
||||||
|
if (my_strcasecmp(table_alias_charset, $1.str, table->db))
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_WRONG_DB_NAME, $1.str);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
if (my_strcasecmp(table_alias_charset, $3.str, table->real_name))
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_WRONG_TABLE_NAME, $3.str);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
$$=$5;
|
||||||
|
}
|
||||||
|
| ident '.' ident
|
||||||
|
{
|
||||||
|
TABLE_LIST *table= (TABLE_LIST*) Select->table_list.first;
|
||||||
|
if (my_strcasecmp(table_alias_charset, $1.str, table->alias))
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_WRONG_TABLE_NAME, $1.str);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
$$=$3;
|
||||||
|
}
|
||||||
| '.' ident { $$=$2;} /* For Delphi */;
|
| '.' ident { $$=$2;} /* For Delphi */;
|
||||||
|
|
||||||
table_ident:
|
table_ident:
|
||||||
|
Reference in New Issue
Block a user