summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorJon Olav Hauglid <jon.hauglid@oracle.com>2010-08-20 10:24:32 +0200
committerJon Olav Hauglid <jon.hauglid@oracle.com>2010-08-20 10:24:32 +0200
commit02863b41804352f1371407ccdc9e1cb51b9e20e3 (patch)
tree454ab81e4898c9102f39f0802a13bbb0a5236f09 /sql
parentac6026ce2774222b4607fcad4e36bbd5c25ff60a (diff)
parent72b228c581e81dc07a6ac61a57b89ce80d0e5847 (diff)
downloadmariadb-git-02863b41804352f1371407ccdc9e1cb51b9e20e3.tar.gz
Merge from mysql-5.5-runtime to mysql-5.5-bugfixing.
Diffstat (limited to 'sql')
-rw-r--r--sql/CMakeLists.txt1
-rw-r--r--sql/Makefile.am6
-rw-r--r--sql/datadict.cc2
-rw-r--r--sql/event_db_repository.cc19
-rw-r--r--sql/events.cc3
-rw-r--r--sql/ha_ndbcluster.cc29
-rw-r--r--sql/ha_ndbcluster_binlog.cc49
-rw-r--r--sql/ha_ndbcluster_binlog.h3
-rw-r--r--sql/handler.cc28
-rw-r--r--sql/handler.h2
-rw-r--r--sql/lock.cc26
-rw-r--r--sql/lock.h42
-rw-r--r--sql/mdl.cc206
-rw-r--r--sql/mdl.h99
-rw-r--r--sql/mysqld.cc15
-rw-r--r--sql/mysqld.h8
-rw-r--r--sql/sql_admin.cc15
-rw-r--r--sql/sql_base.cc661
-rw-r--r--sql/sql_base.h118
-rw-r--r--sql/sql_handler.cc2
-rw-r--r--sql/sql_insert.cc4
-rw-r--r--sql/sql_parse.cc393
-rw-r--r--sql/sql_parse.h2
-rw-r--r--sql/sql_partition.cc2
-rw-r--r--sql/sql_plugin.cc16
-rw-r--r--sql/sql_reload.cc427
-rw-r--r--sql/sql_reload.h26
-rw-r--r--sql/sql_rename.cc21
-rw-r--r--sql/sql_servers.cc4
-rw-r--r--sql/sql_show.cc39
-rw-r--r--sql/sql_table.cc55
-rw-r--r--sql/sql_test.cc4
-rw-r--r--sql/sql_trigger.cc19
-rw-r--r--sql/sql_truncate.cc4
-rw-r--r--sql/sql_view.cc20
-rw-r--r--sql/sql_yacc.yy3
-rw-r--r--sql/sys_vars.cc10
-rw-r--r--sql/table.cc278
-rw-r--r--sql/table.h63
39 files changed, 1554 insertions, 1170 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt
index 0fb203ed0fb..224cf67af7f 100644
--- a/sql/CMakeLists.txt
+++ b/sql/CMakeLists.txt
@@ -74,6 +74,7 @@ SET (SQL_SOURCE
sql_profile.cc event_parse_data.cc sql_alter.cc
sql_signal.cc rpl_handler.cc mdl.cc sql_admin.cc
transaction.cc sys_vars.cc sql_truncate.cc datadict.cc
+ sql_reload.cc
${GEN_SOURCES}
${MYSYS_LIBWRAP_SOURCE})
diff --git a/sql/Makefile.am b/sql/Makefile.am
index 4fb7bfe919c..1bc195a2981 100644
--- a/sql/Makefile.am
+++ b/sql/Makefile.am
@@ -40,6 +40,7 @@ DTRACEFILES = filesort.o \
sql_cursor.o \
sql_delete.o \
sql_truncate.o \
+ sql_reload.o \
sql_insert.o \
datadict.o \
sql_parse.o \
@@ -59,6 +60,7 @@ DTRACEFILES_DEPEND = filesort.o \
sql_cursor.o \
sql_delete.o \
sql_truncate.o \
+ sql_reload.o \
sql_insert.o \
datadict.o \
sql_parse.o \
@@ -126,7 +128,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
contributors.h sql_servers.h sql_signal.h records.h \
sql_prepare.h rpl_handler.h replication.h mdl.h \
sql_plist.h transaction.h sys_vars.h sql_truncate.h \
- sql_admin.h datadict.h
+ sql_admin.h sql_reload.h datadict.h
mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
item.cc item_sum.cc item_buff.cc item_func.cc \
@@ -140,7 +142,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
sql_connect.cc scheduler.cc sql_parse.cc \
keycaches.cc set_var.cc sql_yacc.yy sys_vars.cc \
sql_base.cc table.cc sql_select.cc sql_insert.cc \
- datadict.cc sql_profile.cc \
+ sql_reload.cc datadict.cc sql_profile.cc \
sql_prepare.cc sql_error.cc sql_locale.cc \
sql_update.cc sql_delete.cc uniques.cc sql_do.cc \
procedure.cc sql_test.cc sql_admin.cc \
diff --git a/sql/datadict.cc b/sql/datadict.cc
index 362435a94db..e3f679cc7ec 100644
--- a/sql/datadict.cc
+++ b/sql/datadict.cc
@@ -177,9 +177,7 @@ bool dd_recreate_table(THD *thd, const char *db, const char *table_name)
build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0);
/* Attempt to reconstruct the table. */
- mysql_mutex_lock(&LOCK_open);
error= ha_create_table(thd, path, db, table_name, &create_info, TRUE);
- mysql_mutex_unlock(&LOCK_open);
DBUG_RETURN(error);
}
diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc
index d47f1641bb0..db508e4ea41 100644
--- a/sql/event_db_repository.cc
+++ b/sql/event_db_repository.cc
@@ -996,24 +996,33 @@ Event_db_repository::load_named_event(THD *thd, LEX_STRING dbname,
LEX_STRING name, Event_basic *etn)
{
bool ret;
- TABLE *table= NULL;
ulong saved_mode= thd->variables.sql_mode;
+ Open_tables_backup open_tables_backup;
+ TABLE_LIST event_table;
DBUG_ENTER("Event_db_repository::load_named_event");
DBUG_PRINT("enter",("thd: 0x%lx name: %*s", (long) thd,
(int) name.length, name.str));
+ event_table.init_one_table("mysql", 5, "event", 5, "event", TL_READ);
+
/* Reset sql_mode during data dictionary operations. */
thd->variables.sql_mode= 0;
- if (!(ret= open_event_table(thd, TL_READ, &table)))
+ /*
+ We don't use open_event_table() here to make sure that SHOW
+ CREATE EVENT works properly in transactional context, and
+ does not release transactional metadata locks when the
+ event table is closed.
+ */
+ if (!(ret= open_system_tables_for_read(thd, &event_table, &open_tables_backup)))
{
- if ((ret= find_named_event(dbname, name, table)))
+ if ((ret= find_named_event(dbname, name, event_table.table)))
my_error(ER_EVENT_DOES_NOT_EXIST, MYF(0), name.str);
- else if ((ret= etn->load_from_row(thd, table)))
+ else if ((ret= etn->load_from_row(thd, event_table.table)))
my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "event");
- close_mysql_tables(thd);
+ close_system_tables(thd, &open_tables_backup);
}
thd->variables.sql_mode= saved_mode;
diff --git a/sql/events.cc b/sql/events.cc
index 99304a1c460..10a7535425f 100644
--- a/sql/events.cc
+++ b/sql/events.cc
@@ -699,7 +699,6 @@ send_show_create_event(THD *thd, Event_timed *et, Protocol *protocol)
bool
Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name)
{
- Open_tables_backup open_tables_backup;
Event_timed et;
bool ret;
@@ -722,9 +721,7 @@ Events::show_create_event(THD *thd, LEX_STRING dbname, LEX_STRING name)
deadlock can occur please refer to the description of 'system table'
flag.
*/
- thd->reset_n_backup_open_tables_state(&open_tables_backup);
ret= db_repository->load_named_event(thd, dbname, name, &et);
- thd->restore_backup_open_tables_state(&open_tables_backup);
if (!ret)
ret= send_show_create_event(thd, &et, thd->protocol);
diff --git a/sql/ha_ndbcluster.cc b/sql/ha_ndbcluster.cc
index d4a98265c49..15b017ece81 100644
--- a/sql/ha_ndbcluster.cc
+++ b/sql/ha_ndbcluster.cc
@@ -680,7 +680,7 @@ int ha_ndbcluster::ndb_err(NdbTransaction *trans)
bzero((char*) &table_list,sizeof(table_list));
table_list.db= m_dbname;
table_list.alias= table_list.table_name= m_tabname;
- close_cached_tables(thd, &table_list, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
break;
}
default:
@@ -5702,7 +5702,7 @@ int ha_ndbcluster::create(const char *name,
m_table->getObjectVersion(),
(is_truncate) ?
SOT_TRUNCATE_TABLE : SOT_CREATE_TABLE,
- 0, 0, 1);
+ 0, 0);
break;
}
}
@@ -6143,7 +6143,7 @@ int ha_ndbcluster::rename_table(const char *from, const char *to)
old_dbname, m_tabname,
ndb_table_id, ndb_table_version,
SOT_RENAME_TABLE,
- m_dbname, new_tabname, 1);
+ m_dbname, new_tabname);
}
// If we are moving tables between databases, we need to recreate
@@ -6337,7 +6337,7 @@ retry_temporary_error1:
thd->query(), thd->query_length(),
share->db, share->table_name,
ndb_table_id, ndb_table_version,
- SOT_DROP_TABLE, 0, 0, 1);
+ SOT_DROP_TABLE, 0, 0);
}
else if (table_dropped && share && share->op) /* ndbcluster_log_schema_op
will do a force GCP */
@@ -7019,7 +7019,6 @@ int ndbcluster_drop_database_impl(const char *path)
while ((tabname=it++))
{
tablename_to_filename(tabname, tmp, FN_REFLEN - (tmp - full_path)-1);
- mysql_mutex_lock(&LOCK_open);
if (ha_ndbcluster::delete_table(0, ndb, full_path, dbname, tabname))
{
const NdbError err= dict->getNdbError();
@@ -7029,7 +7028,6 @@ int ndbcluster_drop_database_impl(const char *path)
ret= ndb_to_mysql_error(&err);
}
}
- mysql_mutex_unlock(&LOCK_open);
}
DBUG_RETURN(ret);
}
@@ -7056,7 +7054,7 @@ static void ndbcluster_drop_database(handlerton *hton, char *path)
ha_ndbcluster::set_dbname(path, db);
ndbcluster_log_schema_op(thd, 0,
thd->query(), thd->query_length(),
- db, "", 0, 0, SOT_DROP_DB, 0, 0, 0);
+ db, "", 0, 0, SOT_DROP_DB, 0, 0);
#endif
DBUG_VOID_RETURN;
}
@@ -7181,7 +7179,6 @@ int ndbcluster_find_all_files(THD *thd)
my_free(data);
my_free(pack_data);
- mysql_mutex_lock(&LOCK_open);
if (discover)
{
/* ToDo 4.1 database needs to be created if missing */
@@ -7199,7 +7196,6 @@ int ndbcluster_find_all_files(THD *thd)
TRUE);
}
#endif
- mysql_mutex_unlock(&LOCK_open);
}
}
while (unhandled && retries);
@@ -7292,19 +7288,16 @@ int ndbcluster_find_files(handlerton *hton, THD *thd,
file_name->str, reg_ext, 0);
if (my_access(name, F_OK))
{
- mysql_mutex_lock(&LOCK_open);
DBUG_PRINT("info", ("Table %s listed and need discovery",
file_name->str));
if (ndb_create_table_from_engine(thd, db, file_name->str))
{
- mysql_mutex_unlock(&LOCK_open);
push_warning_printf(current_thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_TABLE_EXISTS_ERROR,
"Discover of table %s.%s failed",
db, file_name->str);
continue;
}
- mysql_mutex_unlock(&LOCK_open);
}
DBUG_PRINT("info", ("%s existed in NDB _and_ on disk ", file_name->str));
file_on_disk= TRUE;
@@ -7361,10 +7354,8 @@ int ndbcluster_find_files(handlerton *hton, THD *thd,
file_name_str= (char*)my_hash_element(&ok_tables, i);
end= end1 +
tablename_to_filename(file_name_str, end1, sizeof(name) - (end1 - name));
- mysql_mutex_lock(&LOCK_open);
ndbcluster_create_binlog_setup(ndb, name, end-name,
db, file_name_str, TRUE);
- mysql_mutex_unlock(&LOCK_open);
}
}
#endif
@@ -7426,7 +7417,6 @@ int ndbcluster_find_files(handlerton *hton, THD *thd,
}
/* Lock mutex before creating .FRM files. */
- mysql_mutex_lock(&LOCK_open);
/* Create new files. */
List_iterator_fast<char> it2(create_list);
while ((file_name_str=it2++))
@@ -7441,8 +7431,6 @@ int ndbcluster_find_files(handlerton *hton, THD *thd,
}
}
- mysql_mutex_unlock(&LOCK_open);
-
my_hash_free(&ok_tables);
my_hash_free(&ndb_tables);
@@ -8452,8 +8440,7 @@ int handle_trailing_share(NDB_SHARE *share)
bzero((char*) &table_list,sizeof(table_list));
table_list.db= share->db;
table_list.alias= table_list.table_name= share->table_name;
- mysql_mutex_assert_owner(&LOCK_open);
- close_cached_tables(thd, &table_list, TRUE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
mysql_mutex_lock(&ndbcluster_mutex);
/* ndb_share reference temporary free */
@@ -10612,13 +10599,13 @@ int ndbcluster_alter_tablespace(handlerton *hton,
thd->query(), thd->query_length(),
"", alter_info->tablespace_name,
0, 0,
- SOT_TABLESPACE, 0, 0, 0);
+ SOT_TABLESPACE, 0, 0);
else
ndbcluster_log_schema_op(thd, 0,
thd->query(), thd->query_length(),
"", alter_info->logfile_group_name,
0, 0,
- SOT_LOGFILE_GROUP, 0, 0, 0);
+ SOT_LOGFILE_GROUP, 0, 0);
#endif
DBUG_RETURN(FALSE);
diff --git a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc
index 26fdb8e1425..861b44f74b1 100644
--- a/sql/ha_ndbcluster_binlog.cc
+++ b/sql/ha_ndbcluster_binlog.cc
@@ -360,7 +360,6 @@ ndbcluster_binlog_open_table(THD *thd, NDB_SHARE *share,
int error;
DBUG_ENTER("ndbcluster_binlog_open_table");
- mysql_mutex_assert_owner(&LOCK_open);
init_tmp_table_share(thd, table_share, share->db, 0, share->table_name,
share->key);
if ((error= open_table_def(thd, table_share, 0)))
@@ -376,7 +375,9 @@ ndbcluster_binlog_open_table(THD *thd, NDB_SHARE *share,
free_table_share(table_share);
DBUG_RETURN(error);
}
+ mysql_mutex_lock(&LOCK_open);
assign_new_table_id(table_share);
+ mysql_mutex_unlock(&LOCK_open);
if (!reopen)
{
@@ -625,7 +626,7 @@ ndbcluster_binlog_log_query(handlerton *hton, THD *thd, enum_binlog_command binl
{
ndbcluster_log_schema_op(thd, 0, query, query_length,
db, table_name, 0, 0, type,
- 0, 0, 0);
+ 0, 0);
}
DBUG_VOID_RETURN;
}
@@ -908,9 +909,7 @@ int ndbcluster_setup_binlog_table_shares(THD *thd)
if (!ndb_schema_share &&
ndbcluster_check_ndb_schema_share() == 0)
{
- mysql_mutex_lock(&LOCK_open);
ndb_create_table_from_engine(thd, NDB_REP_DB, NDB_SCHEMA_TABLE);
- mysql_mutex_unlock(&LOCK_open);
if (!ndb_schema_share)
{
ndbcluster_create_schema_table(thd);
@@ -922,9 +921,7 @@ int ndbcluster_setup_binlog_table_shares(THD *thd)
if (!ndb_apply_status_share &&
ndbcluster_check_ndb_apply_status_share() == 0)
{
- mysql_mutex_lock(&LOCK_open);
ndb_create_table_from_engine(thd, NDB_REP_DB, NDB_APPLY_TABLE);
- mysql_mutex_unlock(&LOCK_open);
if (!ndb_apply_status_share)
{
ndbcluster_create_ndb_apply_status_table(thd);
@@ -934,12 +931,10 @@ int ndbcluster_setup_binlog_table_shares(THD *thd)
}
if (!ndbcluster_find_all_files(thd))
{
- mysql_mutex_lock(&LOCK_open);
ndb_binlog_tables_inited= TRUE;
if (opt_ndb_extra_logging)
sql_print_information("NDB Binlog: ndb tables writable");
- close_cached_tables(NULL, NULL, TRUE, FALSE);
- mysql_mutex_unlock(&LOCK_open);
+ close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT);
/* Signal injector thread that all is setup */
mysql_cond_signal(&injector_cond);
}
@@ -1276,8 +1271,7 @@ int ndbcluster_log_schema_op(THD *thd, NDB_SHARE *share,
uint32 ndb_table_id,
uint32 ndb_table_version,
enum SCHEMA_OP_TYPE type,
- const char *new_db, const char *new_table_name,
- int have_lock_open)
+ const char *new_db, const char *new_table_name)
{
DBUG_ENTER("ndbcluster_log_schema_op");
Thd_ndb *thd_ndb= get_thd_ndb(thd);
@@ -1580,11 +1574,6 @@ end:
int max_timeout= DEFAULT_SYNC_TIMEOUT;
mysql_mutex_lock(&ndb_schema_object->mutex);
- if (have_lock_open)
- {
- mysql_mutex_assert_owner(&LOCK_open);
- mysql_mutex_unlock(&LOCK_open);
- }
while (1)
{
struct timespec abstime;
@@ -1640,10 +1629,6 @@ end:
"distributing", ndb_schema_object->key);
}
}
- if (have_lock_open)
- {
- mysql_mutex_lock(&LOCK_open);
- }
mysql_mutex_unlock(&ndb_schema_object->mutex);
}
@@ -1726,7 +1711,6 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp,
{
DBUG_DUMP("frm", (uchar*) altered_table->getFrmData(),
altered_table->getFrmLength());
- mysql_mutex_lock(&LOCK_open);
Ndb_table_guard ndbtab_g(dict, tabname);
const NDBTAB *old= ndbtab_g.get_table();
if (!old &&
@@ -1752,7 +1736,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp,
bzero((char*) &table_list,sizeof(table_list));
table_list.db= (char *)dbname;
table_list.alias= table_list.table_name= (char *)tabname;
- close_cached_tables(thd, &table_list, TRUE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
if ((error= ndbcluster_binlog_open_table(thd, share,
table_share, table, 1)))
@@ -1763,8 +1747,6 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp,
table_share= share->table_share;
dbname= table_share->db.str;
tabname= table_share->table_name.str;
-
- mysql_mutex_unlock(&LOCK_open);
}
my_free(data);
my_free(pack_data);
@@ -1858,7 +1840,7 @@ ndb_handle_schema_change(THD *thd, Ndb *ndb, NdbEventOperation *pOp,
bzero((char*) &table_list,sizeof(table_list));
table_list.db= (char *)dbname;
table_list.alias= table_list.table_name= (char *)tabname;
- close_cached_tables(thd, &table_list, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
/* ndb_share reference create free */
DBUG_PRINT("NDB_SHARE", ("%s create free use_count: %u",
share->key, share->use_count));
@@ -1979,7 +1961,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb,
bzero((char*) &table_list,sizeof(table_list));
table_list.db= schema->db;
table_list.alias= table_list.table_name= schema->name;
- close_cached_tables(thd, &table_list, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
}
/* ndb_share reference temporary free */
if (share)
@@ -1991,7 +1973,6 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb,
}
// fall through
case SOT_CREATE_TABLE:
- mysql_mutex_lock(&LOCK_open);
if (ndbcluster_check_if_local_table(schema->db, schema->name))
{
DBUG_PRINT("info", ("NDB Binlog: Skipping locally defined table '%s.%s'",
@@ -2005,7 +1986,6 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb,
{
print_could_not_discover_error(thd, schema);
}
- mysql_mutex_unlock(&LOCK_open);
log_query= 1;
break;
case SOT_DROP_DB:
@@ -2096,7 +2076,7 @@ ndb_binlog_thread_handle_schema_event(THD *thd, Ndb *ndb,
mysql_mutex_unlock(&ndb_schema_share_mutex);
/* end protect ndb_schema_share */
- close_cached_tables(NULL, NULL, FALSE, FALSE);
+ close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT);
// fall through
case NDBEVENT::TE_ALTER:
ndb_handle_schema_change(thd, ndb, pOp, tmp_share);
@@ -2253,7 +2233,7 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd,
bzero((char*) &table_list,sizeof(table_list));
table_list.db= schema->db;
table_list.alias= table_list.table_name= schema->name;
- close_cached_tables(thd, &table_list, FALSE, FALSE);
+ close_cached_tables(thd, &table_list, FALSE, LONG_TIMEOUT);
}
if (schema_type != SOT_ALTER_TABLE)
break;
@@ -2274,7 +2254,6 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd,
free_share(&share);
share= 0;
}
- mysql_mutex_lock(&LOCK_open);
if (ndbcluster_check_if_local_table(schema->db, schema->name))
{
DBUG_PRINT("info", ("NDB Binlog: Skipping locally defined table '%s.%s'",
@@ -2288,7 +2267,6 @@ ndb_binlog_thread_handle_schema_event_post_epoch(THD *thd,
{
print_could_not_discover_error(thd, schema);
}
- mysql_mutex_unlock(&LOCK_open);
}
break;
default:
@@ -3154,8 +3132,6 @@ ndbcluster_handle_drop_table(Ndb *ndb, const char *event_name,
#ifdef SYNC_DROP_
thd->proc_info= "Syncing ndb table schema operation and binlog";
mysql_mutex_lock(&share->mutex);
- mysql_mutex_assert_owner(&LOCK_open);
- mysql_mutex_unlock(&LOCK_open);
int max_timeout= DEFAULT_SYNC_TIMEOUT;
while (share->op)
{
@@ -3181,7 +3157,6 @@ ndbcluster_handle_drop_table(Ndb *ndb, const char *event_name,
type_str, share->key);
}
}
- mysql_mutex_lock(&LOCK_open);
mysql_mutex_unlock(&share->mutex);
#else
mysql_mutex_lock(&share->mutex);
@@ -3963,9 +3938,9 @@ restart:
!ndb_binlog_running))
break; /* Shutting down server */
- if (ndb_binlog_index && ndb_binlog_index->s->needs_reopen())
+ if (ndb_binlog_index && ndb_binlog_index->s->has_old_version())
{
- if (ndb_binlog_index->s->needs_reopen())
+ if (ndb_binlog_index->s->has_old_version())
{
trans_commit_stmt(thd);
close_thread_tables(thd);
diff --git a/sql/ha_ndbcluster_binlog.h b/sql/ha_ndbcluster_binlog.h
index 4d2a49588b4..5dbcf0fa43f 100644
--- a/sql/ha_ndbcluster_binlog.h
+++ b/sql/ha_ndbcluster_binlog.h
@@ -158,8 +158,7 @@ int ndbcluster_log_schema_op(THD *thd, NDB_SHARE *share,
uint32 ndb_table_version,
enum SCHEMA_OP_TYPE type,
const char *new_db,
- const char *new_table_name,
- int have_lock_open);
+ const char *new_table_name);
int ndbcluster_handle_drop_table(Ndb *ndb, const char *event_name,
NDB_SHARE *share,
const char *type_str);
diff --git a/sql/handler.cc b/sql/handler.cc
index 9893b3cac16..567dbe6ea49 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -3670,6 +3670,34 @@ int ha_create_table_from_engine(THD* thd, const char *db, const char *name)
DBUG_RETURN(error != 0);
}
+
+/**
+ Try to find a table in a storage engine.
+
+ @param db Normalized table schema name
+ @param name Normalized table name.
+ @param[out] exists Only valid if the function succeeded.
+
+ @retval TRUE An error is found
+ @retval FALSE Success, check *exists
+*/
+
+bool
+ha_check_if_table_exists(THD* thd, const char *db, const char *name,
+ bool *exists)
+{
+ uchar *frmblob= NULL;
+ size_t frmlen;
+ DBUG_ENTER("ha_check_if_table_exists");
+
+ *exists= ! ha_discover(thd, db, name, &frmblob, &frmlen);
+ if (*exists)
+ my_free(frmblob);
+
+ DBUG_RETURN(FALSE);
+}
+
+
void st_ha_check_opt::init()
{
flags= sql_flags= 0;
diff --git a/sql/handler.h b/sql/handler.h
index 83158708837..b1d64a1114b 100644
--- a/sql/handler.h
+++ b/sql/handler.h
@@ -2119,6 +2119,8 @@ bool ha_show_status(THD *thd, handlerton *db_type, enum ha_stat_type stat);
/* discovery */
int ha_create_table_from_engine(THD* thd, const char *db, const char *name);
+bool ha_check_if_table_exists(THD* thd, const char *db, const char *name,
+ bool *exists);
int ha_discover(THD* thd, const char* dbname, const char* name,
uchar** frmblob, size_t* frmlen);
int ha_find_files(THD *thd,const char *db,const char *path,
diff --git a/sql/lock.cc b/sql/lock.cc
index 1a77b576e67..0181a544824 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -1298,27 +1298,19 @@ bool Global_read_lock::make_global_read_lock_block_commit(THD *thd)
/**
- Broadcast COND_refresh and COND_global_read_lock.
-
- Due to a bug in a threading library it could happen that a signal
- did not reach its target. A condition for this was that the same
- condition variable was used with different mutexes in
- mysql_cond_wait(). Some time ago we changed LOCK_open to
- LOCK_global_read_lock in global read lock handling. So COND_refresh
- was used with LOCK_open and LOCK_global_read_lock.
-
- We did now also change from COND_refresh to COND_global_read_lock
- in global read lock handling. But now it is necessary to signal
- both conditions at the same time.
-
- @note
- When signalling COND_global_read_lock within the global read lock
- handling, it is not necessary to also signal COND_refresh.
+ Broadcast COND_global_read_lock.
+
+ TODO/FIXME: Dmitry thinks that we broadcast on COND_global_read_lock
+ when old instance of table is closed to avoid races
+ between incrementing refresh_version and
+ wait_if_global_read_lock(thd, TRUE, FALSE) call.
+ Once global read lock implementation starts using MDL
+ infrastructure this will became unnecessary and should
+ be removed.
*/
void broadcast_refresh(void)
{
- mysql_cond_broadcast(&COND_refresh);
mysql_cond_broadcast(&COND_global_read_lock);
}
diff --git a/sql/lock.h b/sql/lock.h
index 0083dd3ba18..c097c8d269e 100644
--- a/sql/lock.h
+++ b/sql/lock.h
@@ -9,48 +9,6 @@ struct TABLE_LIST;
class THD;
typedef struct st_mysql_lock MYSQL_LOCK;
-/* mysql_lock_tables() and open_table() flags bits */
-#define MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK 0x0001
-#define MYSQL_OPEN_IGNORE_FLUSH 0x0002
-#define MYSQL_OPEN_TEMPORARY_ONLY 0x0004
-#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008
-#define MYSQL_LOCK_LOG_TABLE 0x0010
-/**
- Do not try to acquire a metadata lock on the table: we
- already have one.
-*/
-#define MYSQL_OPEN_HAS_MDL_LOCK 0x0020
-/**
- If in locked tables mode, ignore the locked tables and get
- a new instance of the table.
-*/
-#define MYSQL_OPEN_GET_NEW_TABLE 0x0040
-/** Don't look up the table in the list of temporary tables. */
-#define MYSQL_OPEN_SKIP_TEMPORARY 0x0080
-/** Fail instead of waiting when conficting metadata lock is discovered. */
-#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0100
-/** Open tables using MDL_SHARED lock instead of one specified in parser. */
-#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0200
-/**
- Open tables using MDL_SHARED_HIGH_PRIO lock instead of one specified
- in parser.
-*/
-#define MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL 0x0400
-/**
- When opening or locking the table, use the maximum timeout
- (LONG_TIMEOUT = 1 year) rather than the user-supplied timeout value.
-*/
-#define MYSQL_LOCK_IGNORE_TIMEOUT 0x0800
-
-/** Please refer to the internals manual. */
-#define MYSQL_OPEN_REOPEN (MYSQL_OPEN_IGNORE_FLUSH |\
- MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK |\
- MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\
- MYSQL_LOCK_IGNORE_TIMEOUT |\
- MYSQL_OPEN_GET_NEW_TABLE |\
- MYSQL_OPEN_SKIP_TEMPORARY |\
- MYSQL_OPEN_HAS_MDL_LOCK)
-
MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, uint flags);
void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock);
diff --git a/sql/mdl.cc b/sql/mdl.cc
index ca66799baed..d53ddcee0c8 100644
--- a/sql/mdl.cc
+++ b/sql/mdl.cc
@@ -71,6 +71,21 @@ static void init_mdl_psi_keys(void)
void notify_shared_lock(THD *thd, MDL_ticket *conflicting_ticket);
+/**
+ Thread state names to be used in case when we have to wait on resource
+ belonging to certain namespace.
+*/
+
+const char *MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]=
+{
+ "Waiting for global metadata lock",
+ "Waiting for schema metadata lock",
+ "Waiting for table metadata lock",
+ "Waiting for stored function metadata lock",
+ "Waiting for stored procedure metadata lock",
+ NULL
+};
+
static bool mdl_initialized= 0;
@@ -98,38 +113,32 @@ private:
};
-enum enum_deadlock_weight
-{
- MDL_DEADLOCK_WEIGHT_DML= 0,
- MDL_DEADLOCK_WEIGHT_DDL= 100
-};
-
-
/**
A context of the recursive traversal through all contexts
in all sessions in search for deadlock.
*/
-class Deadlock_detection_visitor
+class Deadlock_detection_visitor: public MDL_wait_for_graph_visitor
{
public:
Deadlock_detection_visitor(MDL_context *start_node_arg)
: m_start_node(start_node_arg),
m_victim(NULL),
- m_current_search_depth(0)
+ m_current_search_depth(0),
+ m_found_deadlock(FALSE)
{}
- bool enter_node(MDL_context * /* unused */);
- void leave_node(MDL_context * /* unused */);
+ virtual bool enter_node(MDL_context *node);
+ virtual void leave_node(MDL_context *node);
- bool inspect_edge(MDL_context *dest);
+ virtual bool inspect_edge(MDL_context *dest);
MDL_context *get_victim() const { return m_victim; }
-
+private:
/**
Change the deadlock victim to a new one if it has lower deadlock
weight.
*/
- MDL_context *opt_change_victim_to(MDL_context *new_victim);
+ void opt_change_victim_to(MDL_context *new_victim);
private:
/**
The context which has initiated the search. There
@@ -145,6 +154,8 @@ private:
loop.
*/
uint m_current_search_depth;
+ /** TRUE if we found a deadlock. */
+ bool m_found_deadlock;
/**
Maximum depth for deadlock searches. After this depth is
achieved we will unconditionally declare that there is a
@@ -167,29 +178,38 @@ private:
a node is entered, inspect_edge() will be called
for all wait-for destinations of this node. Then
leave_node() will be called.
- We call "enter_node()" for all nodes we inspect,
+ We call "enter_node()" for all nodes we inspect,
including the starting node.
@retval TRUE Maximum search depth exceeded.
@retval FALSE OK.
*/
-bool Deadlock_detection_visitor::enter_node(MDL_context * /* unused */)
+bool Deadlock_detection_visitor::enter_node(MDL_context *node)
{
- if (++m_current_search_depth >= MAX_SEARCH_DEPTH)
- return TRUE;
- return FALSE;
+ m_found_deadlock= ++m_current_search_depth >= MAX_SEARCH_DEPTH;
+ if (m_found_deadlock)
+ {
+ DBUG_ASSERT(! m_victim);
+ opt_change_victim_to(node);
+ }
+ return m_found_deadlock;
}
/**
Done inspecting this node. Decrease the search
- depth. Clear the node for debug safety.
+ depth. If a deadlock is found, and we are
+ backtracking to the start node, optionally
+ change the deadlock victim to one with lower
+ deadlock weight.
*/
-void Deadlock_detection_visitor::leave_node(MDL_context * /* unused */)
+void Deadlock_detection_visitor::leave_node(MDL_context *node)
{
--m_current_search_depth;
+ if (m_found_deadlock)
+ opt_change_victim_to(node);
}
@@ -202,7 +222,8 @@ void Deadlock_detection_visitor::leave_node(MDL_context * /* unused */)
bool Deadlock_detection_visitor::inspect_edge(MDL_context *node)
{
- return node == m_start_node;
+ m_found_deadlock= node == m_start_node;
+ return m_found_deadlock;
}
@@ -214,7 +235,7 @@ bool Deadlock_detection_visitor::inspect_edge(MDL_context *node)
@retval !new_victim New victim became the current.
*/
-MDL_context *
+void
Deadlock_detection_visitor::opt_change_victim_to(MDL_context *new_victim)
{
if (m_victim == NULL ||
@@ -223,10 +244,10 @@ Deadlock_detection_visitor::opt_change_victim_to(MDL_context *new_victim)
/* Swap victims, unlock the old one. */
MDL_context *tmp= m_victim;
m_victim= new_victim;
- return tmp;
+ m_victim->lock_deadlock_victim();
+ if (tmp)
+ tmp->unlock_deadlock_victim();
}
- /* No change, unlock the current context. */
- return new_victim;
}
@@ -349,8 +370,8 @@ public:
void remove_ticket(Ticket_list MDL_lock::*queue, MDL_ticket *ticket);
- bool find_deadlock(MDL_ticket *waiting_ticket,
- Deadlock_detection_visitor *dvisitor);
+ bool visit_subgraph(MDL_ticket *waiting_ticket,
+ MDL_wait_for_graph_visitor *gvisitor);
/** List of granted tickets for this lock. */
Ticket_list m_granted;
@@ -479,14 +500,6 @@ mdl_locks_key(const uchar *record, size_t *length,
the associated condition variable: LOCK_mdl and COND_mdl.
These locking primitives are implementation details of the MDL
subsystem and are private to it.
-
- Note, that even though the new implementation adds acquisition
- of a new global mutex to the execution flow of almost every SQL
- statement, the design capitalizes on that to later save on
- look ups in the table definition cache. This leads to reduced
- contention overall and on LOCK_open in particular.
- Please see the description of MDL_context::acquire_lock()
- for details.
*/
void mdl_init()
@@ -876,8 +889,8 @@ void MDL_ticket::destroy(MDL_ticket *ticket)
uint MDL_ticket::get_deadlock_weight() const
{
return (m_lock->key.mdl_namespace() == MDL_key::GLOBAL ||
- m_type > MDL_SHARED_NO_WRITE ?
- MDL_DEADLOCK_WEIGHT_DDL : MDL_DEADLOCK_WEIGHT_DML);
+ m_type >= MDL_SHARED_NO_WRITE ?
+ DEADLOCK_WEIGHT_DDL : DEADLOCK_WEIGHT_DML);
}
@@ -946,17 +959,18 @@ void MDL_wait::reset_status()
Wait for the status to be assigned to this wait slot.
@param abs_timeout Absolute time after which waiting should stop.
- @param set_status_on_tiemout TRUE - If in case of timeout waiting
- context should close the wait slot by
- sending TIMEOUT to itself.
- FALSE - Otherwise.
+ @param set_status_on_timeout TRUE - If in case of timeout waiting
+ context should close the wait slot by
+ sending TIMEOUT to itself.
+ FALSE - Otherwise.
+ @param wait_state_name Thread state name to be set for duration of wait.
@returns Signal posted.
*/
MDL_wait::enum_wait_status
MDL_wait::timed_wait(THD *thd, struct timespec *abs_timeout,
- bool set_status_on_timeout)
+ bool set_status_on_timeout, const char *wait_state_name)
{
const char *old_msg;
enum_wait_status result;
@@ -965,7 +979,7 @@ MDL_wait::timed_wait(THD *thd, struct timespec *abs_timeout,
mysql_mutex_lock(&m_LOCK_wait_status);
old_msg= thd_enter_cond(thd, &m_COND_wait_status, &m_LOCK_wait_status,
- "Waiting for table");
+ wait_state_name);
while (!m_wait_status && !thd_killed(thd) &&
wait_result != ETIMEDOUT && wait_result != ETIME)
@@ -1380,6 +1394,15 @@ bool MDL_lock::has_pending_conflicting_lock(enum_mdl_type type)
}
+MDL_wait_for_graph_visitor::~MDL_wait_for_graph_visitor()
+{
+}
+
+
+MDL_wait_for_subgraph::~MDL_wait_for_subgraph()
+{
+}
+
/**
Check if ticket represents metadata lock of "stronger" or equal type
than specified one. I.e. if metadata lock represented by ticket won't
@@ -1528,9 +1551,8 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request,
MDL_ticket *ticket;
bool is_transactional;
- DBUG_ASSERT(mdl_request->type < MDL_SHARED_NO_WRITE ||
- (is_lock_owner(MDL_key::GLOBAL, "", "",
- MDL_INTENTION_EXCLUSIVE)));
+ DBUG_ASSERT(mdl_request->type != MDL_EXCLUSIVE ||
+ is_lock_owner(MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE));
DBUG_ASSERT(mdl_request->ticket == NULL);
/* Don't take chances in production. */
@@ -1746,7 +1768,8 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout)
while (cmp_timespec(abs_shortwait, abs_timeout) <= 0)
{
/* abs_timeout is far away. Wait a short while and notify locks. */
- wait_status= m_wait.timed_wait(m_thd, &abs_shortwait, FALSE);
+ wait_status= m_wait.timed_wait(m_thd, &abs_shortwait, FALSE,
+ mdl_request->key.get_wait_state_name());
if (wait_status != MDL_wait::EMPTY)
break;
@@ -1757,10 +1780,12 @@ MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout)
set_timespec(abs_shortwait, 1);
}
if (wait_status == MDL_wait::EMPTY)
- wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE);
+ wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE,
+ mdl_request->key.get_wait_state_name());
}
else
- wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE);
+ wait_status= m_wait.timed_wait(m_thd, &abs_timeout, TRUE,
+ mdl_request->key.get_wait_state_name());
done_waiting_for();
@@ -1952,8 +1977,17 @@ MDL_context::upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket,
}
-bool MDL_lock::find_deadlock(MDL_ticket *waiting_ticket,
- Deadlock_detection_visitor *dvisitor)
+/**
+ A fragment of recursive traversal of the wait-for graph
+ in search for deadlocks. Direct the deadlock visitor to all
+ contexts that own the lock the current node in the wait-for
+ graph is waiting for.
+ As long as the initial node is remembered in the visitor,
+ a deadlock is found when the same node is seen twice.
+*/
+
+bool MDL_lock::visit_subgraph(MDL_ticket *waiting_ticket,
+ MDL_wait_for_graph_visitor *gvisitor)
{
MDL_ticket *ticket;
MDL_context *src_ctx= waiting_ticket->get_ctx();
@@ -2022,7 +2056,7 @@ bool MDL_lock::find_deadlock(MDL_ticket *waiting_ticket,
are visiting it but this is OK: in the worst case we might do some
extra work and one more context might be chosen as a victim.
*/
- if (dvisitor->enter_node(src_ctx))
+ if (gvisitor->enter_node(src_ctx))
goto end;
/*
@@ -2036,7 +2070,7 @@ bool MDL_lock::find_deadlock(MDL_ticket *waiting_ticket,
/* Filter out edges that point to the same node. */
if (ticket->get_ctx() != src_ctx &&
ticket->is_incompatible_when_granted(waiting_ticket->get_type()) &&
- dvisitor->inspect_edge(ticket->get_ctx()))
+ gvisitor->inspect_edge(ticket->get_ctx()))
{
goto end_leave_node;
}
@@ -2047,7 +2081,7 @@ bool MDL_lock::find_deadlock(MDL_ticket *waiting_ticket,
/* Filter out edges that point to the same node. */
if (ticket->get_ctx() != src_ctx &&
ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) &&
- dvisitor->inspect_edge(ticket->get_ctx()))
+ gvisitor->inspect_edge(ticket->get_ctx()))
{
goto end_leave_node;
}
@@ -2059,7 +2093,7 @@ bool MDL_lock::find_deadlock(MDL_ticket *waiting_ticket,
{
if (ticket->get_ctx() != src_ctx &&
ticket->is_incompatible_when_granted(waiting_ticket->get_type()) &&
- ticket->get_ctx()->find_deadlock(dvisitor))
+ ticket->get_ctx()->visit_subgraph(gvisitor))
{
goto end_leave_node;
}
@@ -2070,7 +2104,7 @@ bool MDL_lock::find_deadlock(MDL_ticket *waiting_ticket,
{
if (ticket->get_ctx() != src_ctx &&
ticket->is_incompatible_when_waiting(waiting_ticket->get_type()) &&
- ticket->get_ctx()->find_deadlock(dvisitor))
+ ticket->get_ctx()->visit_subgraph(gvisitor))
{
goto end_leave_node;
}
@@ -2079,7 +2113,7 @@ bool MDL_lock::find_deadlock(MDL_ticket *waiting_ticket,
result= FALSE;
end_leave_node:
- dvisitor->leave_node(src_ctx);
+ gvisitor->leave_node(src_ctx);
end:
mysql_prlock_unlock(&m_rwlock);
@@ -2088,35 +2122,47 @@ end:
/**
- Recursively traverse the wait-for graph of MDL contexts
- in search for deadlocks.
+ Traverse a portion of wait-for graph which is reachable
+ through the edge represented by this ticket and search
+ for deadlocks.
- @retval TRUE A deadlock is found. A victim is remembered
- by the visitor.
+ @retval TRUE A deadlock is found. A pointer to deadlock
+ victim is saved in the visitor.
@retval FALSE
*/
-bool MDL_context::find_deadlock(Deadlock_detection_visitor *dvisitor)
+bool MDL_ticket::accept_visitor(MDL_wait_for_graph_visitor *gvisitor)
+{
+ return m_lock->visit_subgraph(this, gvisitor);
+}
+
+
+/**
+ A fragment of recursive traversal of the wait-for graph of
+ MDL contexts in the server in search for deadlocks.
+ Assume this MDL context is a node in the wait-for graph,
+ and direct the visitor to all adjacent nodes. As long
+ as the starting node is remembered in the visitor, a
+ deadlock is found when the same node is visited twice.
+ One MDL context is connected to another in the wait-for
+ graph if it waits on a resource that is held by the other
+ context.
+
+ @retval TRUE A deadlock is found. A pointer to deadlock
+ victim is saved in the visitor.
+ @retval FALSE
+*/
+
+bool MDL_context::visit_subgraph(MDL_wait_for_graph_visitor *gvisitor)
{
- MDL_context *m_unlock_ctx= this;
bool result= FALSE;
mysql_prlock_rdlock(&m_LOCK_waiting_for);
if (m_waiting_for)
- {
- result= m_waiting_for->m_lock->find_deadlock(m_waiting_for, dvisitor);
- if (result)
- m_unlock_ctx= dvisitor->opt_change_victim_to(this);
- }
- /*
- We may recurse into the same MDL_context more than once
- in case this is not the starting node. Make sure we release the
- read lock as it's been taken, except for 1 read lock for
- the deadlock victim.
- */
- if (m_unlock_ctx)
- mysql_prlock_unlock(&m_unlock_ctx->m_LOCK_waiting_for);
+ result= m_waiting_for->accept_visitor(gvisitor);
+
+ mysql_prlock_unlock(&m_LOCK_waiting_for);
return result;
}
@@ -2138,14 +2184,14 @@ void MDL_context::find_deadlock()
while (1)
{
/*
- The fact that we use fresh instance of dvisitor for each
+ The fact that we use fresh instance of gvisitor for each
search performed by find_deadlock() below is important,
the code responsible for victim selection relies on this.
*/
Deadlock_detection_visitor dvisitor(this);
MDL_context *victim;
- if (! find_deadlock(&dvisitor))
+ if (! visit_subgraph(&dvisitor))
{
/* No deadlocks are found! */
break;
@@ -2166,7 +2212,7 @@ void MDL_context::find_deadlock()
context was waiting is concurrently satisfied.
*/
(void) victim->m_wait.set_status(MDL_wait::VICTIM);
- mysql_prlock_unlock(&victim->m_LOCK_waiting_for);
+ victim->unlock_deadlock_victim();
if (victim == this)
break;
diff --git a/sql/mdl.h b/sql/mdl.h
index c8acd69c0f1..7938d833eac 100644
--- a/sql/mdl.h
+++ b/sql/mdl.h
@@ -34,7 +34,6 @@ class THD;
class MDL_context;
class MDL_lock;
class MDL_ticket;
-class Deadlock_detection_visitor;
/**
Type of metadata lock request.
@@ -184,7 +183,9 @@ public:
TABLE,
FUNCTION,
PROCEDURE,
- TRIGGER };
+ TRIGGER,
+ /* This should be the last ! */
+ NAMESPACE_END };
const uchar *ptr() const { return (uchar*) m_ptr; }
uint length() const { return m_length; }
@@ -251,10 +252,20 @@ public:
}
MDL_key() {} /* To use when part of MDL_request. */
+ /**
+ Get thread state name to be used in case when we have to
+ wait on resource identified by key.
+ */
+ const char * get_wait_state_name() const
+ {
+ return m_namespace_to_wait_state_name[(int)mdl_namespace()];
+ }
+
private:
uint16 m_length;
uint16 m_db_name_length;
char m_ptr[MAX_MDLKEY_LENGTH];
+ static const char * m_namespace_to_wait_state_name[NAMESPACE_END];
private:
MDL_key(const MDL_key &); /* not implemented */
MDL_key &operator=(const MDL_key &); /* not implemented */
@@ -360,6 +371,59 @@ public:
typedef void (*mdl_cached_object_release_hook)(void *);
+
+/**
+ An abstract class for inspection of a connected
+ subgraph of the wait-for graph.
+*/
+
+class MDL_wait_for_graph_visitor
+{
+public:
+ virtual bool enter_node(MDL_context *node) = 0;
+ virtual void leave_node(MDL_context *node) = 0;
+
+ virtual bool inspect_edge(MDL_context *dest) = 0;
+ virtual ~MDL_wait_for_graph_visitor();
+ MDL_wait_for_graph_visitor() :m_lock_open_count(0) {}
+public:
+ /**
+ XXX, hack: During deadlock search, we may need to
+ inspect TABLE_SHAREs and acquire LOCK_open. Since
+ LOCK_open is not a recursive mutex, count here how many
+ times we "took" it (but only take and release once).
+ Not using a native recursive mutex or rwlock in 5.5 for
+ LOCK_open since it has significant performance impacts.
+ */
+ uint m_lock_open_count;
+};
+
+/**
+ Abstract class representing an edge in the waiters graph
+ to be traversed by deadlock detection algorithm.
+*/
+
+class MDL_wait_for_subgraph
+{
+public:
+ virtual ~MDL_wait_for_subgraph();
+
+ /**
+ Accept a wait-for graph visitor to inspect the node
+ this edge is leading to.
+ */
+ virtual bool accept_visitor(MDL_wait_for_graph_visitor *gvisitor) = 0;
+
+ enum enum_deadlock_weight
+ {
+ DEADLOCK_WEIGHT_DML= 0,
+ DEADLOCK_WEIGHT_DDL= 100
+ };
+ /* A helper used to determine which lock request should be aborted. */
+ virtual uint get_deadlock_weight() const = 0;
+};
+
+
/**
A granted metadata lock.
@@ -380,7 +444,7 @@ typedef void (*mdl_cached_object_release_hook)(void *);
threads/contexts.
*/
-class MDL_ticket
+class MDL_ticket : public MDL_wait_for_subgraph
{
public:
/**
@@ -414,8 +478,9 @@ public:
bool is_incompatible_when_granted(enum_mdl_type type) const;
bool is_incompatible_when_waiting(enum_mdl_type type) const;
- /* A helper used to determine which lock request should be aborted. */
- uint get_deadlock_weight() const;
+ /** Implement MDL_wait_for_subgraph interface. */
+ virtual bool accept_visitor(MDL_wait_for_graph_visitor *dvisitor);
+ virtual uint get_deadlock_weight() const;
private:
friend class MDL_context;
@@ -462,7 +527,7 @@ public:
enum_wait_status get_status();
void reset_status();
enum_wait_status timed_wait(THD *thd, struct timespec *abs_timeout,
- bool signal_timeout);
+ bool signal_timeout, const char *wait_state_name);
private:
/**
Condvar which is used for waiting until this context's pending
@@ -582,8 +647,6 @@ public:
{
return m_needs_thr_lock_abort;
}
-
- bool find_deadlock(Deadlock_detection_visitor *dvisitor);
public:
/**
If our request for a lock is scheduled, or aborted by the deadlock
@@ -675,12 +738,13 @@ private:
*/
mysql_prlock_t m_LOCK_waiting_for;
/**
- Tell the deadlock detector what lock this session is waiting for.
+ Tell the deadlock detector what metadata lock or table
+ definition cache entry this session is waiting for.
In principle, this is redundant, as information can be found
by inspecting waiting queues, but we'd very much like it to be
readily available to the wait-for graph iterator.
*/
- MDL_ticket *m_waiting_for;
+ MDL_wait_for_subgraph *m_waiting_for;
private:
MDL_ticket *find_ticket(MDL_request *mdl_req,
bool *is_transactional);
@@ -688,13 +752,16 @@ private:
bool try_acquire_lock_impl(MDL_request *mdl_request,
MDL_ticket **out_ticket);
+public:
void find_deadlock();
+ bool visit_subgraph(MDL_wait_for_graph_visitor *dvisitor);
+
/** Inform the deadlock detector there is an edge in the wait-for graph. */
- void will_wait_for(MDL_ticket *pending_ticket)
+ void will_wait_for(MDL_wait_for_subgraph *waiting_for_arg)
{
mysql_prlock_wrlock(&m_LOCK_waiting_for);
- m_waiting_for= pending_ticket;
+ m_waiting_for= waiting_for_arg;
mysql_prlock_unlock(&m_LOCK_waiting_for);
}
@@ -705,6 +772,14 @@ private:
m_waiting_for= NULL;
mysql_prlock_unlock(&m_LOCK_waiting_for);
}
+ void lock_deadlock_victim()
+ {
+ mysql_prlock_rdlock(&m_LOCK_waiting_for);
+ }
+ void unlock_deadlock_victim()
+ {
+ mysql_prlock_unlock(&m_LOCK_waiting_for);
+ }
private:
MDL_context(const MDL_context &rhs); /* not implemented */
MDL_context &operator=(MDL_context &rhs); /* not implemented */
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index a0eca1b78ff..0cd3ecdf47d 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -87,6 +87,7 @@
#include <errmsg.h>
#include "sp_rcontext.h"
#include "sp_cache.h"
+#include "sql_reload.h" // reload_acl_and_cache
#ifdef HAVE_POLL_H
#include <poll.h>
@@ -601,7 +602,7 @@ SHOW_COMP_OPTION have_profiling;
pthread_key(MEM_ROOT**,THR_MALLOC);
pthread_key(THD*, THR_THD);
mysql_mutex_t LOCK_thread_count;
-mysql_mutex_t LOCK_open,
+mysql_mutex_t
LOCK_status, LOCK_global_read_lock,
LOCK_error_log, LOCK_uuid_generator,
LOCK_delayed_insert, LOCK_delayed_status, LOCK_delayed_create,
@@ -623,7 +624,7 @@ mysql_mutex_t LOCK_des_key_file;
mysql_rwlock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave;
mysql_rwlock_t LOCK_system_variables_hash;
mysql_cond_t COND_thread_count;
-mysql_cond_t COND_refresh, COND_global_read_lock;
+mysql_cond_t COND_global_read_lock;
pthread_t signal_thread;
pthread_attr_t connection_attrib;
mysql_mutex_t LOCK_server_started;
@@ -1526,7 +1527,6 @@ static void wait_for_signal_thread_to_end()
static void clean_up_mutexes()
{
mysql_rwlock_destroy(&LOCK_grant);
- mysql_mutex_destroy(&LOCK_open);
mysql_mutex_destroy(&LOCK_thread_count);
mysql_mutex_destroy(&LOCK_status);
mysql_mutex_destroy(&LOCK_delayed_insert);
@@ -1559,7 +1559,6 @@ static void clean_up_mutexes()
mysql_mutex_destroy(&LOCK_prepared_stmt_count);
mysql_mutex_destroy(&LOCK_error_messages);
mysql_cond_destroy(&COND_thread_count);
- mysql_cond_destroy(&COND_refresh);
mysql_cond_destroy(&COND_global_read_lock);
mysql_cond_destroy(&COND_thread_cache);
mysql_cond_destroy(&COND_flush_thread_cache);
@@ -3500,7 +3499,6 @@ You should consider changing lower_case_table_names to 1 or 2",
static int init_thread_environment()
{
- mysql_mutex_init(key_LOCK_open, &LOCK_open, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_LOCK_thread_count, &LOCK_thread_count, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_LOCK_status, &LOCK_status, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_LOCK_delayed_insert,
@@ -3547,7 +3545,6 @@ static int init_thread_environment()
mysql_rwlock_init(key_rwlock_LOCK_sys_init_slave, &LOCK_sys_init_slave);
mysql_rwlock_init(key_rwlock_LOCK_grant, &LOCK_grant);
mysql_cond_init(key_COND_thread_count, &COND_thread_count, NULL);
- mysql_cond_init(key_COND_refresh, &COND_refresh, NULL);
mysql_cond_init(key_COND_global_read_lock, &COND_global_read_lock, NULL);
mysql_cond_init(key_COND_thread_cache, &COND_thread_cache, NULL);
mysql_cond_init(key_COND_flush_thread_cache, &COND_flush_thread_cache, NULL);
@@ -7682,7 +7679,7 @@ PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_prep_xids,
key_LOCK_delayed_insert, key_LOCK_delayed_status, key_LOCK_error_log,
key_LOCK_gdl, key_LOCK_global_read_lock, key_LOCK_global_system_variables,
key_LOCK_manager,
- key_LOCK_open, key_LOCK_prepared_stmt_count,
+ key_LOCK_prepared_stmt_count,
key_LOCK_rpl_status, key_LOCK_server_started, key_LOCK_status,
key_LOCK_system_variables_hash, key_LOCK_table_share, key_LOCK_thd_data,
key_LOCK_user_conn, key_LOCK_uuid_generator, key_LOG_LOCK_log,
@@ -7721,7 +7718,6 @@ static PSI_mutex_info all_server_mutexes[]=
{ &key_LOCK_global_read_lock, "LOCK_global_read_lock", PSI_FLAG_GLOBAL},
{ &key_LOCK_global_system_variables, "LOCK_global_system_variables", PSI_FLAG_GLOBAL},
{ &key_LOCK_manager, "LOCK_manager", PSI_FLAG_GLOBAL},
- { &key_LOCK_open, "LOCK_open", PSI_FLAG_GLOBAL},
{ &key_LOCK_prepared_stmt_count, "LOCK_prepared_stmt_count", PSI_FLAG_GLOBAL},
{ &key_LOCK_rpl_status, "LOCK_rpl_status", PSI_FLAG_GLOBAL},
{ &key_LOCK_server_started, "LOCK_server_started", PSI_FLAG_GLOBAL},
@@ -7769,7 +7765,7 @@ PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool;
PSI_cond_key key_BINLOG_COND_prep_xids, key_BINLOG_update_cond,
key_COND_cache_status_changed, key_COND_global_read_lock, key_COND_manager,
- key_COND_refresh, key_COND_rpl_status, key_COND_server_started,
+ key_COND_rpl_status, key_COND_server_started,
key_delayed_insert_cond, key_delayed_insert_cond_client,
key_item_func_sleep_cond, key_master_info_data_cond,
key_master_info_start_cond, key_master_info_stop_cond,
@@ -7793,7 +7789,6 @@ static PSI_cond_info all_server_conds[]=
{ &key_COND_cache_status_changed, "Query_cache::COND_cache_status_changed", 0},
{ &key_COND_global_read_lock, "COND_global_read_lock", PSI_FLAG_GLOBAL},
{ &key_COND_manager, "COND_manager", PSI_FLAG_GLOBAL},
- { &key_COND_refresh, "COND_refresh", PSI_FLAG_GLOBAL},
{ &key_COND_rpl_status, "COND_rpl_status", PSI_FLAG_GLOBAL},
{ &key_COND_server_started, "COND_server_started", PSI_FLAG_GLOBAL},
{ &key_delayed_insert_cond, "Delayed_insert::cond", 0},
diff --git a/sql/mysqld.h b/sql/mysqld.h
index 9605128166d..71b52416302 100644
--- a/sql/mysqld.h
+++ b/sql/mysqld.h
@@ -229,7 +229,7 @@ extern PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_prep_xids,
key_LOCK_delayed_insert, key_LOCK_delayed_status, key_LOCK_error_log,
key_LOCK_gdl, key_LOCK_global_read_lock, key_LOCK_global_system_variables,
key_LOCK_logger, key_LOCK_manager,
- key_LOCK_open, key_LOCK_prepared_stmt_count,
+ key_LOCK_prepared_stmt_count,
key_LOCK_rpl_status, key_LOCK_server_started, key_LOCK_status,
key_LOCK_table_share, key_LOCK_thd_data,
key_LOCK_user_conn, key_LOCK_uuid_generator, key_LOG_LOCK_log,
@@ -249,7 +249,7 @@ extern PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool;
extern PSI_cond_key key_BINLOG_COND_prep_xids, key_BINLOG_update_cond,
key_COND_cache_status_changed, key_COND_global_read_lock, key_COND_manager,
- key_COND_refresh, key_COND_rpl_status, key_COND_server_started,
+ key_COND_rpl_status, key_COND_server_started,
key_delayed_insert_cond, key_delayed_insert_cond_client,
key_item_func_sleep_cond, key_master_info_data_cond,
key_master_info_start_cond, key_master_info_stop_cond,
@@ -316,7 +316,7 @@ extern MYSQL_PLUGIN_IMPORT key_map key_map_full; /* Should be threaded
/*
Server mutex locks and condition variables.
*/
-extern mysql_mutex_t LOCK_open,
+extern mysql_mutex_t
LOCK_user_locks, LOCK_status,
LOCK_error_log, LOCK_delayed_insert, LOCK_uuid_generator,
LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_timezone,
@@ -332,7 +332,7 @@ extern mysql_cond_t COND_server_started;
extern mysql_rwlock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave;
extern mysql_rwlock_t LOCK_system_variables_hash;
extern mysql_cond_t COND_thread_count;
-extern mysql_cond_t COND_refresh, COND_manager;
+extern mysql_cond_t COND_manager;
extern mysql_cond_t COND_global_read_lock;
extern int32 thread_running;
extern my_atomic_rwlock_t thread_running_lock;
diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc
index 6f0405bd4a8..1f96f1cf0e4 100644
--- a/sql/sql_admin.cc
+++ b/sql/sql_admin.cc
@@ -95,20 +95,19 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list,
hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length);
mysql_mutex_lock(&LOCK_open);
- if (!(share= (get_table_share(thd, table_list, key, key_length, 0,
- &error, hash_value))))
- {
- mysql_mutex_unlock(&LOCK_open);
+ share= get_table_share(thd, table_list, key, key_length, 0,
+ &error, hash_value);
+ mysql_mutex_unlock(&LOCK_open);
+ if (share == NULL)
DBUG_RETURN(0); // Can't open frm file
- }
if (open_table_from_share(thd, share, "", 0, 0, 0, &tmp_table, FALSE))
{
+ mysql_mutex_lock(&LOCK_open);
release_table_share(share);
mysql_mutex_unlock(&LOCK_open);
DBUG_RETURN(0); // Out of memory
}
- mysql_mutex_unlock(&LOCK_open);
table= &tmp_table;
}
@@ -772,10 +771,8 @@ send_result_message:
}
else if (open_for_modify || fatal_error)
{
- mysql_mutex_lock(&LOCK_open);
tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED,
- table->db, table->table_name);
- mysql_mutex_unlock(&LOCK_open);
+ table->db, table->table_name, FALSE);
/*
May be something modified. Consequently, we have to
invalidate the query cache.
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 8be29eb12f5..01ab0b6dec5 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -59,42 +59,13 @@
#endif
-/**
- This internal handler is used to trap internally
- errors that can occur when executing open table
- during the prelocking phase.
-*/
-class Prelock_error_handler : public Internal_error_handler
-{
-public:
- Prelock_error_handler()
- : m_handled_errors(0), m_unhandled_errors(0)
- {}
-
- virtual ~Prelock_error_handler() {}
-
- virtual bool handle_condition(THD *thd,
- uint sql_errno,
- const char* sqlstate,
- MYSQL_ERROR::enum_warning_level level,
- const char* msg,
- MYSQL_ERROR ** cond_hdl);
-
- bool safely_trapped_errors();
-
-private:
- int m_handled_errors;
- int m_unhandled_errors;
-};
-
-
bool
-Prelock_error_handler::handle_condition(THD *,
- uint sql_errno,
- const char*,
- MYSQL_ERROR::enum_warning_level,
- const char*,
- MYSQL_ERROR ** cond_hdl)
+No_such_table_error_handler::handle_condition(THD *,
+ uint sql_errno,
+ const char*,
+ MYSQL_ERROR::enum_warning_level,
+ const char*,
+ MYSQL_ERROR ** cond_hdl)
{
*cond_hdl= NULL;
if (sql_errno == ER_NO_SUCH_TABLE)
@@ -108,7 +79,7 @@ Prelock_error_handler::handle_condition(THD *,
}
-bool Prelock_error_handler::safely_trapped_errors()
+bool No_such_table_error_handler::safely_trapped_errors()
{
/*
If m_unhandled_errors != 0, something else, unanticipated, happened,
@@ -124,6 +95,38 @@ bool Prelock_error_handler::safely_trapped_errors()
*/
/**
+ Protects table_def_hash, used and unused lists in the
+ TABLE_SHARE object, LRU lists of used TABLEs and used
+ TABLE_SHAREs, refresh_version and the table id counter.
+*/
+mysql_mutex_t LOCK_open;
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key key_LOCK_open;
+static PSI_mutex_info all_tdc_mutexes[]= {
+ { &key_LOCK_open, "LOCK_open", PSI_FLAG_GLOBAL }
+};
+
+/**
+ Initialize performance schema instrumentation points
+ used by the table cache.
+*/
+
+static void init_tdc_psi_keys(void)
+{
+ const char *category= "sql";
+ int count;
+
+ if (PSI_server == NULL)
+ return;
+
+ count= array_elements(all_tdc_mutexes);
+ PSI_server->register_mutex(category, all_tdc_mutexes, count);
+}
+#endif /* HAVE_PSI_INTERFACE */
+
+
+/**
Total number of TABLE instances for tables in the table definition cache
(both in use by threads and not in use). This value is accessible to user
as "Open_tables" status variable.
@@ -146,9 +149,6 @@ static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables,
static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry);
static bool auto_repair_table(THD *thd, TABLE_LIST *table_list);
static void free_cache_entry(TABLE *entry);
-static bool tdc_wait_for_old_versions(THD *thd,
- MDL_request_list *mdl_requests,
- ulong timeout);
static bool
has_write_table_with_auto_increment(TABLE_LIST *tables);
@@ -294,9 +294,14 @@ static void table_def_free_entry(TABLE_SHARE *share)
bool table_def_init(void)
{
table_def_inited= 1;
+#ifdef HAVE_PSI_INTERFACE
+ init_tdc_psi_keys();
+#endif
+ mysql_mutex_init(key_LOCK_open, &LOCK_open, MY_MUTEX_INIT_FAST);
oldest_unused_share= &end_of_unused_share;
end_of_unused_share.prev= &oldest_unused_share;
+
return my_hash_init(&table_def_cache, &my_charset_bin, table_def_size,
0, 0, table_def_key,
(my_hash_free_key) table_def_free_entry, 0) != 0;
@@ -314,8 +319,6 @@ void table_def_start_shutdown(void)
if (table_def_inited)
{
mysql_mutex_lock(&LOCK_open);
- /* Free all cached but unused TABLEs and TABLE_SHAREs first. */
- close_cached_tables(NULL, NULL, TRUE, FALSE);
/*
Ensure that TABLE and TABLE_SHARE objects which are created for
tables that are open during process of plugins' shutdown are
@@ -324,6 +327,8 @@ void table_def_start_shutdown(void)
*/
table_def_shutdown_in_progress= TRUE;
mysql_mutex_unlock(&LOCK_open);
+ /* Free all cached but unused TABLEs and TABLE_SHAREs. */
+ close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT);
}
}
@@ -336,6 +341,7 @@ void table_def_free(void)
table_def_inited= 0;
/* Free table definitions. */
my_hash_free(&table_def_cache);
+ mysql_mutex_destroy(&LOCK_open);
}
DBUG_VOID_RETURN;
}
@@ -441,7 +447,7 @@ static void table_def_unuse_table(TABLE *table)
DBUG_ASSERT(table->in_use);
/* We shouldn't put the table to 'unused' list if the share is old. */
- DBUG_ASSERT(! table->s->needs_reopen());
+ DBUG_ASSERT(! table->s->has_old_version());
table->in_use= 0;
/* Remove table from the list of tables used in this share. */
@@ -516,10 +522,10 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key,
}
/*
- We assign a new table id under the protection of the LOCK_open and
- the share's own mutex. We do this insted of creating a new mutex
+ We assign a new table id under the protection of LOCK_open.
+ We do this instead of creating a new mutex
and using it for the sole purpose of serializing accesses to a
- static variable, we assign the table id here. We assign it to the
+ static variable, we assign the table id here. We assign it to the
share before inserting it into the table_def_cache to be really
sure that it cannot be read from the cache without having a table
id assigned.
@@ -547,7 +553,7 @@ TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key,
DBUG_RETURN(share);
found:
- /*
+ /*
We found an existing table definition. Return it if we didn't get
an error when reading the table definition from file.
*/
@@ -589,21 +595,21 @@ found:
}
-/*
+/**
Get a table share. If it didn't exist, try creating it from engine
- For arguments and return values, see get_table_from_share()
+ For arguments and return values, see get_table_share()
*/
-static TABLE_SHARE
-*get_table_share_with_create(THD *thd, TABLE_LIST *table_list,
- char *key, uint key_length,
- uint db_flags, int *error,
- my_hash_value_type hash_value)
+static TABLE_SHARE *
+get_table_share_with_discover(THD *thd, TABLE_LIST *table_list,
+ char *key, uint key_length,
+ uint db_flags, int *error,
+ my_hash_value_type hash_value)
{
TABLE_SHARE *share;
- int tmp;
+ bool exists;
DBUG_ENTER("get_table_share_with_create");
share= get_table_share(thd, table_list, key, key_length, db_flags, error,
@@ -617,10 +623,15 @@ static TABLE_SHARE
from the pre-locking list. In this case we still need to try
auto-discover before returning a NULL share.
+ Or, we're inside SHOW CREATE VIEW, which
+ also installs a silencer for ER_NO_SUCH_TABLE error.
+
If share is NULL and the error is ER_NO_SUCH_TABLE, this is
- the same as above, only that the error was not silenced by
- pre-locking. Once again, we need to try to auto-discover
- the share.
+ the same as above, only that the error was not silenced by
+ pre-locking or SHOW CREATE VIEW.
+
+ In both these cases it won't harm to try to discover the
+ table.
Finally, if share is still NULL, it's a real error and we need
to abort.
@@ -628,20 +639,25 @@ static TABLE_SHARE
@todo Rework alternative ways to deal with ER_NO_SUCH TABLE.
*/
if (share || (thd->is_error() && thd->stmt_da->sql_errno() != ER_NO_SUCH_TABLE))
-
DBUG_RETURN(share);
+ *error= 0;
+
/* Table didn't exist. Check if some engine can provide it */
- tmp= ha_create_table_from_engine(thd, table_list->db,
- table_list->table_name);
- if (tmp < 0)
+ if (ha_check_if_table_exists(thd, table_list->db, table_list->table_name,
+ &exists))
+ {
+ thd->clear_error();
+ /* Conventionally, the storage engine API does not report errors. */
+ my_error(ER_OUT_OF_RESOURCES, MYF(0));
+ }
+ else if (! exists)
{
/*
No such table in any engine.
Hide "Table doesn't exist" errors if the table belongs to a view.
The check for thd->is_error() is necessary to not push an
- unwanted error in case of pre-locking, which silences
- "no such table" errors.
+ unwanted error in case the error was already silenced.
@todo Rework the alternative ways to deal with ER_NO_SUCH TABLE.
*/
if (thd->is_error())
@@ -659,27 +675,16 @@ static TABLE_SHARE
view->view_db.str, view->view_name.str);
}
}
- DBUG_RETURN(0);
}
- if (tmp)
+ else
{
- /* Give right error message */
thd->clear_error();
- DBUG_PRINT("error", ("Discovery of %s/%s failed", table_list->db,
- table_list->table_name));
- my_printf_error(ER_UNKNOWN_ERROR,
- "Failed to open '%-.64s', error while "
- "unpacking from engine",
- MYF(0), table_list->table_name);
- DBUG_RETURN(0);
+ *error= 7; /* Run auto-discover. */
}
- /* Table existed in engine. Let's open it */
- thd->warning_info->clear_warning_info(thd->query_id);
- thd->clear_error(); // Clear error message
- DBUG_RETURN(get_table_share(thd, table_list, key, key_length,
- db_flags, error, hash_value));
+ DBUG_RETURN(NULL);
}
+
/**
Mark that we are not using table share anymore.
@@ -703,7 +708,7 @@ void release_table_share(TABLE_SHARE *share)
DBUG_ASSERT(share->ref_count);
if (!--share->ref_count)
{
- if (share->needs_reopen() || table_def_shutdown_in_progress)
+ if (share->has_old_version() || table_def_shutdown_in_progress)
my_hash_delete(&table_def_cache, (uchar*) share);
else
{
@@ -890,7 +895,7 @@ void free_io_cache(TABLE *table)
@param share Table share.
- @pre Caller should have LOCK_open mutex acquired.
+ @pre Caller should have LOCK_open mutex.
*/
static void kill_delayed_threads_for_table(TABLE_SHARE *share)
@@ -926,8 +931,8 @@ static void kill_delayed_threads_for_table(TABLE_SHARE *share)
@param thd Thread context
@param tables List of tables to remove from the cache
- @param have_lock If LOCK_open is locked
@param wait_for_refresh Wait for a impending flush
+ @param timeout Timeout for waiting for flush to be completed.
@note THD can be NULL, but then wait_for_refresh must be FALSE
and tables must be NULL.
@@ -940,19 +945,28 @@ static void kill_delayed_threads_for_table(TABLE_SHARE *share)
lock taken by thread trying to obtain global read lock.
*/
-bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
- bool wait_for_refresh)
+bool close_cached_tables(THD *thd, TABLE_LIST *tables,
+ bool wait_for_refresh, ulong timeout)
{
bool result= FALSE;
bool found= TRUE;
+ struct timespec abstime;
DBUG_ENTER("close_cached_tables");
DBUG_ASSERT(thd || (!wait_for_refresh && !tables));
- if (!have_lock)
- mysql_mutex_lock(&LOCK_open);
+ mysql_mutex_lock(&LOCK_open);
if (!tables)
{
- refresh_version++; // Force close of open tables
+ /*
+ Force close of all open tables.
+
+ Note that code in TABLE_SHARE::wait_for_old_version() assumes that
+ incrementing of refresh_version and removal of unused tables and
+ shares from TDC happens atomically under protection of LOCK_open,
+ or putting it another way that TDC does not contain old shares
+ which don't have any tables used.
+ */
+ refresh_version++;
DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu",
refresh_version));
kill_delayed_threads();
@@ -978,7 +992,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
kill_delayed_threads_for_table(share);
/* tdc_remove_table() also sets TABLE_SHARE::version to 0. */
tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db,
- table->table_name);
+ table->table_name, TRUE);
found=1;
}
}
@@ -986,14 +1000,12 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
wait_for_refresh=0; // Nothing to wait for
}
- if (!have_lock)
- mysql_mutex_unlock(&LOCK_open);
+ mysql_mutex_unlock(&LOCK_open);
if (!wait_for_refresh)
DBUG_RETURN(result);
- /* Code below assume that LOCK_open is released. */
- DBUG_ASSERT(!have_lock);
+ set_timespec(abstime, timeout);
if (thd->locked_tables_mode)
{
@@ -1034,6 +1046,7 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
while (found && ! thd->killed)
{
+ TABLE_SHARE *share;
found= FALSE;
/*
To a self-deadlock or deadlocks with other FLUSH threads
@@ -1044,15 +1057,12 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
mysql_mutex_lock(&LOCK_open);
- thd->enter_cond(&COND_refresh, &LOCK_open, "Flushing tables");
-
if (!tables)
{
for (uint idx=0 ; idx < table_def_cache.records ; idx++)
{
- TABLE_SHARE *share=(TABLE_SHARE*) my_hash_element(&table_def_cache,
- idx);
- if (share->needs_reopen())
+ share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx);
+ if (share->has_old_version())
{
found= TRUE;
break;
@@ -1063,8 +1073,8 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
{
for (TABLE_LIST *table= tables; table; table= table->next_local)
{
- TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name);
- if (share && share->needs_reopen())
+ share= get_cached_table_share(table->db, table->table_name);
+ if (share && share->has_old_version())
{
found= TRUE;
break;
@@ -1074,11 +1084,20 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
if (found)
{
- DBUG_PRINT("signal", ("Waiting for COND_refresh"));
- mysql_cond_wait(&COND_refresh, &LOCK_open);
+ /*
+ The method below temporarily unlocks LOCK_open and frees
+ share's memory.
+ */
+ if (share->wait_for_old_version(thd, &abstime,
+ MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL))
+ {
+ mysql_mutex_unlock(&LOCK_open);
+ result= TRUE;
+ goto err_with_reopen;
+ }
}
- thd->exit_cond(NULL);
+ mysql_mutex_unlock(&LOCK_open);
}
err_with_reopen:
@@ -1102,13 +1121,12 @@ err_with_reopen:
}
-/*
+/**
Close all tables which match specified connection string or
if specified string is NULL, then any table with a connection string.
*/
-bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh,
- LEX_STRING *connection, bool have_lock)
+bool close_cached_connection_tables(THD *thd, LEX_STRING *connection)
{
uint idx;
TABLE_LIST tmp, *tables= NULL;
@@ -1118,8 +1136,7 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh,
bzero(&tmp, sizeof(TABLE_LIST));
- if (!have_lock)
- mysql_mutex_lock(&LOCK_open);
+ mysql_mutex_lock(&LOCK_open);
for (idx= 0; idx < table_def_cache.records; idx++)
{
@@ -1147,21 +1164,10 @@ bool close_cached_connection_tables(THD *thd, bool if_wait_for_refresh,
tables= (TABLE_LIST *) memdup_root(thd->mem_root, (char*)&tmp,
sizeof(TABLE_LIST));
}
+ mysql_mutex_unlock(&LOCK_open);
if (tables)
- result= close_cached_tables(thd, tables, TRUE, FALSE);
-
- if (!have_lock)
- mysql_mutex_unlock(&LOCK_open);
-
- if (if_wait_for_refresh)
- {
- mysql_mutex_lock(&thd->mysys_var->mutex);
- thd->mysys_var->current_mutex= 0;
- thd->mysys_var->current_cond= 0;
- thd->proc_info=0;
- mysql_mutex_unlock(&thd->mysys_var->mutex);
- }
+ result= close_cached_tables(thd, tables, FALSE, LONG_TIMEOUT);
DBUG_RETURN(result);
}
@@ -1355,9 +1361,8 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
}
}
/* Remove the table share from the cache. */
- mysql_mutex_lock(&LOCK_open);
- tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name);
- mysql_mutex_unlock(&LOCK_open);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name,
+ FALSE);
/*
There could be a FLUSH thread waiting
on the table to go away. Wake it up.
@@ -1500,9 +1505,6 @@ void close_thread_tables(THD *thd)
thd->lock=0;
}
/*
- Note that we need to hold LOCK_open while changing the
- open_tables list. Another thread may work on it.
- (See: mysql_notify_thread_having_shared_lock())
Closing a MERGE child before the parent would be fatal if the
other thread tries to abort the MERGE lock in between.
*/
@@ -1548,7 +1550,7 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
mysql_mutex_lock(&LOCK_open);
- if (table->s->needs_reopen() || table->needs_reopen() ||
+ if (table->s->has_old_version() || table->needs_reopen() ||
table_def_shutdown_in_progress)
{
free_cache_entry(table);
@@ -2208,10 +2210,9 @@ bool wait_while_table_is_used(THD *thd, TABLE *table,
table->mdl_ticket, thd->variables.lock_wait_timeout))
DBUG_RETURN(TRUE);
- mysql_mutex_lock(&LOCK_open);
tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN,
- table->s->db.str, table->s->table_name.str);
- mysql_mutex_unlock(&LOCK_open);
+ table->s->db.str, table->s->table_name.str,
+ FALSE);
/* extra() call must come only after all instances above are closed */
(void) table->file->extra(function);
DBUG_RETURN(FALSE);
@@ -2252,9 +2253,8 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name,
table->file->extra(HA_EXTRA_PREPARE_FOR_DROP);
close_thread_table(thd, &thd->open_tables);
/* Remove the table share from the table cache. */
- mysql_mutex_lock(&LOCK_open);
- tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db_name, table_name);
- mysql_mutex_unlock(&LOCK_open);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db_name, table_name,
+ FALSE);
/* Remove the table from the storage engine and rm the .frm. */
quick_rm_table(table_type, db_name, table_name, 0);
}
@@ -2272,8 +2272,6 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name,
exists and to FALSE otherwise.
@note This function acquires LOCK_open internally.
- It also assumes that the fact that there are no exclusive
- metadata locks on the table was checked beforehand.
@note If there is no .FRM file for the table but it exists in one
of engines (e.g. it was created on another node of NDB cluster)
@@ -2286,16 +2284,20 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name,
bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists)
{
char path[FN_REFLEN + 1];
- int rc= 0;
+ TABLE_SHARE *share;
DBUG_ENTER("check_if_table_exists");
- mysql_mutex_assert_not_owner(&LOCK_open);
-
*exists= TRUE;
+ DBUG_ASSERT(thd->mdl_context.
+ is_lock_owner(MDL_key::TABLE, table->db,
+ table->table_name, MDL_SHARED));
+
mysql_mutex_lock(&LOCK_open);
+ share= get_cached_table_share(table->db, table->table_name);
+ mysql_mutex_unlock(&LOCK_open);
- if (get_cached_table_share(table->db, table->table_name))
+ if (share)
goto end;
build_table_filename(path, sizeof(path) - 1, table->db, table->table_name,
@@ -2305,24 +2307,14 @@ bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists)
goto end;
/* .FRM file doesn't exist. Check if some engine can provide it. */
-
- rc= ha_create_table_from_engine(thd, table->db, table->table_name);
-
- if (rc < 0)
+ if (ha_check_if_table_exists(thd, table->db, table->table_name, exists))
{
- /* Table does not exists in engines as well. */
- *exists= FALSE;
- rc= 0;
- }
- else if (rc)
- {
- my_printf_error(ER_UNKNOWN_ERROR, "Failed to open '%-.64s', error while "
+ my_printf_error(ER_OUT_OF_RESOURCES, "Failed to open '%-.64s', error while "
"unpacking from engine", MYF(0), table->table_name);
+ DBUG_RETURN(TRUE);
}
-
end:
- mysql_mutex_unlock(&LOCK_open);
- DBUG_RETURN(test(rc));
+ DBUG_RETURN(FALSE);
}
@@ -2372,8 +2364,9 @@ bool MDL_deadlock_handler::handle_condition(THD *,
{
/* Disable the handler to avoid infinite recursion. */
m_is_active= TRUE;
- (void) m_ot_ctx->request_backoff_action(Open_table_context::OT_MDL_CONFLICT,
- NULL);
+ (void) m_ot_ctx->request_backoff_action(
+ Open_table_context::OT_BACKOFF_AND_RETRY,
+ NULL);
m_is_active= FALSE;
/*
If the above back-off request failed, a new instance of
@@ -2419,6 +2412,8 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx,
uint flags,
MDL_ticket **mdl_ticket)
{
+ MDL_request mdl_request_shared;
+
if (flags & (MYSQL_OPEN_FORCE_SHARED_MDL |
MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL))
{
@@ -2444,16 +2439,12 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx,
DBUG_ASSERT(!(flags & MYSQL_OPEN_FORCE_SHARED_MDL) ||
!(flags & MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL));
- mdl_request= new (thd->mem_root) MDL_request(mdl_request);
- if (mdl_request == NULL)
- return TRUE;
-
- mdl_request->set_type((flags & MYSQL_OPEN_FORCE_SHARED_MDL) ?
- MDL_SHARED : MDL_SHARED_HIGH_PRIO);
+ mdl_request_shared.init(&mdl_request->key,
+ (flags & MYSQL_OPEN_FORCE_SHARED_MDL) ?
+ MDL_SHARED : MDL_SHARED_HIGH_PRIO);
+ mdl_request= &mdl_request_shared;
}
- ot_ctx->add_request(mdl_request);
-
if (flags & MYSQL_OPEN_FAIL_ON_MDL_CONFLICT)
{
/*
@@ -2516,6 +2507,40 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx,
}
+/**
+ Check if table's share is being removed from the table definition
+ cache and, if yes, wait until the flush is complete.
+
+ @param thd Thread context.
+ @param table_list Table which share should be checked.
+ @param timeout Timeout for waiting.
+ @param deadlock_weight Weight of this wait for deadlock detector.
+
+ @retval FALSE Success. Share is up to date or has been flushed.
+ @retval TRUE Error (OOM, our was killed, the wait resulted
+ in a deadlock or timeout). Reported.
+*/
+
+static bool
+tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name,
+ ulong wait_timeout, uint deadlock_weight)
+{
+ TABLE_SHARE *share;
+ bool res= FALSE;
+
+ mysql_mutex_lock(&LOCK_open);
+ if ((share= get_cached_table_share(db, table_name)) &&
+ share->has_old_version())
+ {
+ struct timespec abstime;
+ set_timespec(abstime, wait_timeout);
+ res= share->wait_for_old_version(thd, &abstime, deadlock_weight);
+ }
+ mysql_mutex_unlock(&LOCK_open);
+ return res;
+}
+
+
/*
Open a table.
@@ -2601,8 +2626,8 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
if (thd->open_tables && thd->open_tables->s->version != refresh_version)
{
- (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC,
- NULL);
+ (void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES,
+ NULL);
DBUG_RETURN(TRUE);
}
}
@@ -2813,13 +2838,29 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
else if (table_list->open_strategy == TABLE_LIST::OPEN_STUB)
DBUG_RETURN(FALSE);
+retry_share:
+
mysql_mutex_lock(&LOCK_open);
- if (!(share= get_table_share_with_create(thd, table_list, key,
- key_length, OPEN_VIEW,
- &error,
- hash_value)))
- goto err_unlock2;
+ if (!(share= get_table_share_with_discover(thd, table_list, key,
+ key_length, OPEN_VIEW,
+ &error,
+ hash_value)))
+ {
+ mysql_mutex_unlock(&LOCK_open);
+ /*
+ If thd->is_error() is not set, we either need discover
+ (error == 7), or the error was silenced by the prelocking
+ handler (error == 0), in which case we should skip this
+ table.
+ */
+ if (error == 7 && !thd->is_error())
+ {
+ (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER,
+ table_list);
+ }
+ DBUG_RETURN(TRUE);
+ }
if (share->is_view)
{
@@ -2870,31 +2911,50 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
goto err_unlock;
- /*
- If the version changes while we're opening the tables,
- we have to back off, close all the tables opened-so-far,
- and try to reopen them. Note: refresh_version is currently
- changed only during FLUSH TABLES.
- */
- if (share->needs_reopen() ||
- (thd->open_tables && thd->open_tables->s->version != share->version))
+ if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
{
- if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
+ if (share->has_old_version())
{
- /*
- We already have an MDL lock. But we have encountered an old
- version of table in the table definition cache which is possible
- when someone changes the table version directly in the cache
- without acquiring a metadata lock (e.g. this can happen during
- "rolling" FLUSH TABLE(S)).
- Note, that to avoid a "busywait" in this case, we have to wait
- separately in the caller for old table versions to go away
- (see tdc_wait_for_old_versions()).
- */
+ /*
+ We already have an MDL lock. But we have encountered an old
+ version of table in the table definition cache which is possible
+ when someone changes the table version directly in the cache
+ without acquiring a metadata lock (e.g. this can happen during
+ "rolling" FLUSH TABLE(S)).
+ Release our reference to share, wait until old version of
+ share goes away and then try to get new version of table share.
+ */
+ MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
+ bool wait_result;
+
+ release_table_share(share);
+ mysql_mutex_unlock(&LOCK_open);
+
+ thd->push_internal_handler(&mdl_deadlock_handler);
+ wait_result= tdc_wait_for_old_version(thd, table_list->db,
+ table_list->table_name,
+ ot_ctx->get_timeout(),
+ mdl_ticket->get_deadlock_weight());
+ thd->pop_internal_handler();
+
+ if (wait_result)
+ DBUG_RETURN(TRUE);
+
+ goto retry_share;
+ }
+
+ if (thd->open_tables && thd->open_tables->s->version != share->version)
+ {
+ /*
+ If the version changes while we're opening the tables,
+ we have to back off, close all the tables opened-so-far,
+ and try to reopen them. Note: refresh_version is currently
+ changed only during FLUSH TABLES.
+ */
release_table_share(share);
mysql_mutex_unlock(&LOCK_open);
- (void) ot_ctx->request_backoff_action(Open_table_context::OT_WAIT_TDC,
- NULL);
+ (void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES,
+ NULL);
DBUG_RETURN(TRUE);
}
}
@@ -3004,7 +3064,6 @@ err_lock:
mysql_mutex_lock(&LOCK_open);
err_unlock:
release_table_share(share);
-err_unlock2:
mysql_mutex_unlock(&LOCK_open);
DBUG_RETURN(TRUE);
@@ -3426,7 +3485,7 @@ Locked_tables_list::reopen_tables(THD *thd)
PRE-CONDITION(S)
share is non-NULL
- The LOCK_open mutex is locked
+ The LOCK_open mutex is locked.
POST-CONDITION(S)
@@ -3627,10 +3686,10 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
cache_key_length);
mysql_mutex_lock(&LOCK_open);
- if (!(share= get_table_share_with_create(thd, table_list, cache_key,
- cache_key_length,
- OPEN_VIEW, &error,
- hash_value)))
+ if (!(share= get_table_share(thd, table_list, cache_key,
+ cache_key_length,
+ OPEN_VIEW, &error,
+ hash_value)))
goto err;
if (share->is_view &&
@@ -3732,10 +3791,10 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list)
cache_key_length);
mysql_mutex_lock(&LOCK_open);
- if (!(share= get_table_share_with_create(thd, table_list, cache_key,
- cache_key_length,
- OPEN_VIEW, &not_used,
- hash_value)))
+ if (!(share= get_table_share(thd, table_list, cache_key,
+ cache_key_length,
+ OPEN_VIEW, &not_used,
+ hash_value)))
goto end_unlock;
if (share->is_view)
@@ -3780,7 +3839,8 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list)
release_table_share(share);
/* Remove the repaired share from the table cache. */
tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
- table_list->db, table_list->table_name);
+ table_list->db, table_list->table_name,
+ TRUE);
end_unlock:
mysql_mutex_unlock(&LOCK_open);
return result;
@@ -3814,7 +3874,7 @@ request_backoff_action(enum_open_table_action action_arg,
TABLE_LIST *table)
{
/*
- A back off action may be one of the three kinds:
+ A back off action may be one of three kinds:
* We met a broken table that needs repair, or a table that
is not present on this MySQL server and needs re-discovery.
@@ -3823,27 +3883,47 @@ request_backoff_action(enum_open_table_action action_arg,
locks is very deadlock-prone. If this is a multi- statement
transaction that holds metadata locks for completed
statements, we don't do it, and report an error instead.
+ The action type in this case is OT_DISCOVER or OT_REPAIR.
* Our attempt to acquire an MDL lock lead to a deadlock,
detected by the MDL deadlock detector. The current
session was chosen a victim. If this is a multi-statement
- transaction that holds metadata locks for completed statements,
- restarting locking for the current statement may lead
- to a livelock. Thus, again, if m_has_locks is set,
+ transaction that holds metadata locks taken by completed
+ statements, restarting locking for the current statement
+ may lead to a livelock. Releasing locks of completed
+ statements can not be done as will lead to violation
+ of ACID. Thus, again, if m_has_locks is set,
we report an error. Otherwise, when there are no metadata
locks other than which belong to this statement, we can
try to recover from error by releasing all locks and
restarting the pre-locking.
- * Finally, we could have met a TABLE_SHARE with old version.
- Again, if this is a first statement in a transaction we can
- close all tables, release all metadata locks and wait for
- the old version to go away. Otherwise, waiting with MDL locks
- may lead to criss-cross wait between this connection and a
- connection that has an open table and waits on a metadata lock,
- i.e. to a deadlock.
- Since there is no way to detect such a deadlock, we prevent
- it by reporting an error.
+ Similarly, a deadlock error can occur when the
+ pre-locking process met a TABLE_SHARE that is being
+ flushed, and unsuccessfully waited for the flush to
+ complete. A deadlock in this case can happen, e.g.,
+ when our session is holding a metadata lock that
+ is being waited on by a session which is using
+ the table which is being flushed. The only way
+ to recover from this error is, again, to close all
+ open tables, release all locks, and retry pre-locking.
+ Action type name is OT_REOPEN_TABLES. Re-trying
+ while holding some locks may lead to a livelock,
+ and thus we don't do it.
+ * Finally, this session has open TABLEs from different
+ "generations" of the table cache. This can happen, e.g.,
+ when, after this session has successfully opened one
+ table used for a statement, FLUSH TABLES interfered and
+ expelled another table used in it. FLUSH TABLES then
+ blocks and waits on the table already opened by this
+ statement.
+ We detect this situation by ensuring that table cache
+ version of all tables used in a statement is the same.
+ If it isn't, all tables needs to be reopened.
+ Note, that we can always perform a reopen in this case,
+ even if we already have metadata locks, since we don't
+ keep tables open between statements and a livelock
+ is not possible.
*/
- if (m_has_locks)
+ if (action_arg != OT_REOPEN_TABLES && m_has_locks)
{
my_error(ER_LOCK_DEADLOCK, MYF(0));
return TRUE;
@@ -3889,11 +3969,9 @@ recover_from_failed_open(THD *thd)
/* Execute the action. */
switch (m_action)
{
- case OT_MDL_CONFLICT:
+ case OT_BACKOFF_AND_RETRY:
break;
- case OT_WAIT_TDC:
- result= tdc_wait_for_old_versions(thd, &m_mdl_requests, get_timeout());
- DBUG_ASSERT(thd->mysys_var->current_mutex == NULL);
+ case OT_REOPEN_TABLES:
break;
case OT_DISCOVER:
{
@@ -3902,12 +3980,10 @@ recover_from_failed_open(THD *thd)
MYSQL_OPEN_SKIP_TEMPORARY)))
break;
- mysql_mutex_lock(&LOCK_open);
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, m_failed_table->db,
- m_failed_table->table_name);
+ m_failed_table->table_name, FALSE);
ha_create_table_from_engine(thd, m_failed_table->db,
m_failed_table->table_name);
- mysql_mutex_unlock(&LOCK_open);
thd->warning_info->clear_warning_info(thd->query_id);
thd->clear_error(); // Clear error message
@@ -3921,10 +3997,8 @@ recover_from_failed_open(THD *thd)
MYSQL_OPEN_SKIP_TEMPORARY)))
break;
- mysql_mutex_lock(&LOCK_open);
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, m_failed_table->db,
- m_failed_table->table_name);
- mysql_mutex_unlock(&LOCK_open);
+ m_failed_table->table_name, FALSE);
result= auto_repair_table(thd, m_failed_table);
thd->mdl_context.release_transactional_locks();
@@ -3933,8 +4007,6 @@ recover_from_failed_open(THD *thd)
default:
DBUG_ASSERT(0);
}
- /* Remove all old requests, they will be re-added. */
- m_mdl_requests.empty();
/*
Reset the pointers to conflicting MDL request and the
TABLE_LIST element, set when we need auto-discovery or repair,
@@ -4055,8 +4127,6 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first ||
mdl_type != MDL_key::PROCEDURE)
{
- ot_ctx->add_request(&rt->mdl_request);
-
/*
Since we acquire only shared lock on routines we don't
need to care about global intention exclusive locks.
@@ -4261,11 +4331,11 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables,
The real failure will occur when/if a statement attempts to use
that table.
*/
- Prelock_error_handler prelock_handler;
- thd->push_internal_handler(& prelock_handler);
+ No_such_table_error_handler no_such_table_handler;
+ thd->push_internal_handler(&no_such_table_handler);
error= open_table(thd, tables, new_frm_mem, ot_ctx);
thd->pop_internal_handler();
- safe_to_ignore_table= prelock_handler.safely_trapped_errors();
+ safe_to_ignore_table= no_such_table_handler.safely_trapped_errors();
}
else
error= open_table(thd, tables, new_frm_mem, ot_ctx);
@@ -4733,6 +4803,8 @@ restart:
}
goto err;
}
+
+ DEBUG_SYNC(thd, "open_tables_after_open_and_process_table");
}
/*
@@ -5346,8 +5418,14 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags)
DBUG_RETURN(0);
end:
- /* No need to rollback statement transaction, it's not started. */
- DBUG_ASSERT(thd->transaction.stmt.is_empty());
+ /*
+ No need to commit/rollback the statement transaction: it's
+ either not started or we're filling in an INFORMATION_SCHEMA
+ table on the fly, and thus mustn't manipulate with the
+ transaction of the enclosing statement.
+ */
+ DBUG_ASSERT(thd->transaction.stmt.is_empty() ||
+ (thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
close_thread_tables(thd);
/* Don't keep locks for a failed statement. */
thd->mdl_context.rollback_to_savepoint(mdl_savepoint);
@@ -8535,7 +8613,7 @@ void tdc_flush_unused_tables()
mysql_mutex_lock(&LOCK_open);
while (unused_tables)
free_cache_entry(unused_tables);
- (void) mysql_mutex_unlock(&LOCK_open);
+ mysql_mutex_unlock(&LOCK_open);
}
@@ -8601,17 +8679,6 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
}
mysql_mutex_unlock(&in_use->LOCK_thd_data);
}
- /*
- Wake up threads waiting in tdc_wait_for_old_versions().
- Normally such threads would already get blocked
- in MDL subsystem, when trying to acquire a shared lock.
- But in case a thread has an open HANDLER statement,
- (and thus already grabbed a metadata lock), it gets
- blocked only too late -- at the table cache level.
- Starting from 5.5, this could also easily happen in
- a multi-statement transaction.
- */
- broadcast_refresh();
return signalled;
}
@@ -8640,21 +8707,27 @@ bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
remove TABLE_SHARE).
@param db Name of database
@param table_name Name of table
+ @param has_lock If TRUE, LOCK_open is already acquired
- @note Unlike remove_table_from_cache() it assumes that table instances
- are already not used by any (other) thread (this should be achieved
- by using meta-data locks).
+ @note It assumes that table instances are already not used by any
+ (other) thread (this should be achieved by using meta-data locks).
*/
void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
- const char *db, const char *table_name)
+ const char *db, const char *table_name,
+ bool has_lock)
{
char key[MAX_DBKEY_LENGTH];
uint key_length;
TABLE *table;
TABLE_SHARE *share;
- mysql_mutex_assert_owner(&LOCK_open);
+ if (! has_lock)
+ mysql_mutex_lock(&LOCK_open);
+ else
+ {
+ mysql_mutex_assert_owner(&LOCK_open);
+ }
DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED ||
thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name,
@@ -8686,6 +8759,13 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
/*
Set share's version to zero in order to ensure that it gets
automatically deleted once it is no longer referenced.
+
+ Note that code in TABLE_SHARE::wait_for_old_version() assumes
+ that marking share as old and removal of its unused tables
+ and of the share itself from TDC happens atomically under
+ protection of LOCK_open, or, putting it another way, that
+ TDC does not contain old shares which don't have any tables
+ used.
*/
share->version= 0;
@@ -8695,84 +8775,9 @@ void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
else
(void) my_hash_delete(&table_def_cache, (uchar*) share);
}
-}
-
-
-/**
- Wait until there are no old versions of tables in the table
- definition cache for the metadata locks that we try to acquire.
-
- @param thd Thread context
- @param context Metadata locking context with locks.
- @param timeout Seconds to wait before reporting ER_LOCK_WAIT_TIMEOUT.
-*/
-
-static bool
-tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests,
- ulong timeout)
-{
- TABLE_SHARE *share;
- const char *old_msg;
- MDL_request *mdl_request;
- struct timespec abstime;
- set_timespec(abstime, timeout);
- int wait_result= 0;
- while (!thd->killed)
- {
- /*
- We have to get rid of HANDLERs which are open by this thread
- and have old TABLE versions. Otherwise we might get a deadlock
- in situation when we are waiting for an old TABLE object which
- corresponds to a HANDLER open by another session. And this
- other session waits for our HANDLER object to get closed.
-
- TODO: We should also investigate in which situations we have
- to broadcast on COND_refresh because of this.
- */
- mysql_ha_flush(thd);
-
- mysql_mutex_lock(&LOCK_open);
-
- MDL_request_list::Iterator it(*mdl_requests);
- while ((mdl_request= it++))
- {
- /* Skip requests on non-TDC objects. */
- if (mdl_request->key.mdl_namespace() != MDL_key::TABLE)
- continue;
-
- if ((share= get_cached_table_share(mdl_request->key.db_name(),
- mdl_request->key.name())) &&
- share->needs_reopen())
- break;
- }
- if (!mdl_request)
- {
- /*
- Reset wait_result here in case this was the final check
- after getting a timeout from mysql_cond_timedwait().
- */
- wait_result= 0;
- mysql_mutex_unlock(&LOCK_open);
- break;
- }
- if (wait_result == ETIMEDOUT || wait_result == ETIME)
- {
- /*
- Test for timeout here instead of right after mysql_cond_timedwait().
- This allows for a final iteration and a final check before reporting
- ER_LOCK_WAIT_TIMEOUT.
- */
- mysql_mutex_unlock(&LOCK_open);
- my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
- break;
- }
- old_msg= thd->enter_cond(&COND_refresh, &LOCK_open, "Waiting for table");
- wait_result= mysql_cond_timedwait(&COND_refresh, &LOCK_open, &abstime);
- /* LOCK_open mutex is unlocked by THD::exit_cond() as side-effect. */
- thd->exit_cond(old_msg);
- }
- return thd->killed || wait_result == ETIMEDOUT || wait_result == ETIME;
+ if (! has_lock)
+ mysql_mutex_unlock(&LOCK_open);
}
diff --git a/sql/sql_base.h b/sql/sql_base.h
index e83c3cf8097..ff935c3fc09 100644
--- a/sql/sql_base.h
+++ b/sql/sql_base.h
@@ -70,6 +70,7 @@ enum enum_tdc_remove_table_type {TDC_RT_REMOVE_ALL, TDC_RT_REMOVE_NOT_OWN,
#define RTFC_CHECK_KILLED_FLAG 0x0004
bool check_dup(const char *db, const char *name, TABLE_LIST *tables);
+extern mysql_mutex_t LOCK_open;
bool table_cache_init(void);
void table_cache_free(void);
bool table_def_init(void);
@@ -88,21 +89,51 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name);
TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update,
uint lock_flags);
+
+/* mysql_lock_tables() and open_table() flags bits */
+#define MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK 0x0001
+#define MYSQL_OPEN_IGNORE_FLUSH 0x0002
+#define MYSQL_OPEN_TEMPORARY_ONLY 0x0004
+#define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY 0x0008
+#define MYSQL_LOCK_LOG_TABLE 0x0010
+/**
+ Do not try to acquire a metadata lock on the table: we
+ already have one.
+*/
+#define MYSQL_OPEN_HAS_MDL_LOCK 0x0020
+/**
+ If in locked tables mode, ignore the locked tables and get
+ a new instance of the table.
+*/
+#define MYSQL_OPEN_GET_NEW_TABLE 0x0040
+/** Don't look up the table in the list of temporary tables. */
+#define MYSQL_OPEN_SKIP_TEMPORARY 0x0080
+/** Fail instead of waiting when conficting metadata lock is discovered. */
+#define MYSQL_OPEN_FAIL_ON_MDL_CONFLICT 0x0100
+/** Open tables using MDL_SHARED lock instead of one specified in parser. */
+#define MYSQL_OPEN_FORCE_SHARED_MDL 0x0200
+/**
+ Open tables using MDL_SHARED_HIGH_PRIO lock instead of one specified
+ in parser.
+*/
+#define MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL 0x0400
+/**
+ When opening or locking the table, use the maximum timeout
+ (LONG_TIMEOUT = 1 year) rather than the user-supplied timeout value.
+*/
+#define MYSQL_LOCK_IGNORE_TIMEOUT 0x0800
+
+/** Please refer to the internals manual. */
+#define MYSQL_OPEN_REOPEN (MYSQL_OPEN_IGNORE_FLUSH |\
+ MYSQL_OPEN_IGNORE_GLOBAL_READ_LOCK |\
+ MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\
+ MYSQL_LOCK_IGNORE_TIMEOUT |\
+ MYSQL_OPEN_GET_NEW_TABLE |\
+ MYSQL_OPEN_SKIP_TEMPORARY |\
+ MYSQL_OPEN_HAS_MDL_LOCK)
+
bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
Open_table_context *ot_ctx);
-bool name_lock_locked_table(THD *thd, TABLE_LIST *tables);
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in);
-TABLE *table_cache_insert_placeholder(THD *thd, const char *key,
- uint key_length);
-bool lock_table_name_if_not_cached(THD *thd, const char *db,
- const char *table_name, TABLE **table);
-void detach_merge_children(TABLE *table, bool clear_refs);
-bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
- TABLE_LIST *new_child_list, TABLE_LIST **new_last);
-bool reopen_table(TABLE *table);
-bool reopen_tables(THD *thd,bool get_locks,bool in_refresh);
-void close_data_files_and_morph_locks(THD *thd, const char *db,
- const char *table_name);
bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
uint db_stat, uint prgflag,
uint ha_open_flags, TABLE *outparam, TABLE_LIST *table_desc,
@@ -185,12 +216,9 @@ bool setup_tables_and_check_access(THD *thd,
ulong want_access);
bool wait_while_table_is_used(THD *thd, TABLE *table,
enum ha_extra_function function);
-void unlink_open_table(THD *thd, TABLE *find, bool unlock);
void drop_open_table(THD *thd, TABLE *table, const char *db_name,
const char *table_name);
-void close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
- bool remove_from_locked_tables);
void update_non_unique_table_error(TABLE_LIST *update,
const char *operation,
TABLE_LIST *duplicate);
@@ -226,8 +254,6 @@ void close_temporary_table(THD *thd, TABLE *table, bool free_share,
void close_temporary(TABLE *table, bool free_share, bool delete_table);
bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db,
const char *table_name);
-void mysql_wait_completed_table(ALTER_PARTITION_PARAM_TYPE *lpt, TABLE *my_table);
-void remove_db_from_cache(const char *db);
bool is_equal(const LEX_STRING *a, const LEX_STRING *b);
/* Functions to work with system tables. */
@@ -243,16 +269,15 @@ TABLE *open_performance_schema_table(THD *thd, TABLE_LIST *one_table,
Open_tables_state *backup);
void close_performance_schema_table(THD *thd, Open_tables_state *backup);
-bool close_cached_tables(THD *thd, TABLE_LIST *tables, bool have_lock,
- bool wait_for_refresh);
-bool close_cached_connection_tables(THD *thd, bool wait_for_refresh,
- LEX_STRING *connect_string,
- bool have_lock = FALSE);
+bool close_cached_tables(THD *thd, TABLE_LIST *tables,
+ bool wait_for_refresh, ulong timeout);
+bool close_cached_connection_tables(THD *thd, LEX_STRING *connect_string);
+void close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
+ bool remove_from_locked_tables);
OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild);
-bool remove_table_from_cache(THD *thd, const char *db, const char *table,
- uint flags);
void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
- const char *db, const char *table_name);
+ const char *db, const char *table_name,
+ bool has_lock);
bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
char *cache_key, uint cache_key_length,
MEM_ROOT *mem_root, uint flags);
@@ -263,12 +288,10 @@ TABLE *find_table_for_mdl_upgrade(TABLE *list, const char *db,
void mark_tmp_table_for_reuse(TABLE *table);
bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists);
-extern uint table_cache_count;
extern TABLE *unused_tables;
extern Item **not_found_item;
extern Field *not_found_field;
extern Field *view_ref_found;
-extern HASH open_cache;
extern HASH table_def_cache;
/**
@@ -446,8 +469,8 @@ public:
enum enum_open_table_action
{
OT_NO_ACTION= 0,
- OT_MDL_CONFLICT,
- OT_WAIT_TDC,
+ OT_BACKOFF_AND_RETRY,
+ OT_REOPEN_TABLES,
OT_DISCOVER,
OT_REPAIR
};
@@ -457,9 +480,6 @@ public:
bool request_backoff_action(enum_open_table_action action_arg,
TABLE_LIST *table);
- void add_request(MDL_request *request)
- { m_mdl_requests.push_front(request); }
-
bool can_recover_from_failed_open() const
{ return m_action != OT_NO_ACTION; }
@@ -481,8 +501,6 @@ public:
uint get_flags() const { return m_flags; }
private:
- /** List of requests for all locks taken so far. Used for waiting on locks. */
- MDL_request_list m_mdl_requests;
/**
For OT_DISCOVER and OT_REPAIR actions, the table list element for
the table which definition should be re-discovered or which
@@ -508,4 +526,34 @@ private:
};
+/**
+ This internal handler is used to trap ER_NO_SUCH_TABLE.
+*/
+
+class No_such_table_error_handler : public Internal_error_handler
+{
+public:
+ No_such_table_error_handler()
+ : m_handled_errors(0), m_unhandled_errors(0)
+ {}
+
+ bool handle_condition(THD *thd,
+ uint sql_errno,
+ const char* sqlstate,
+ MYSQL_ERROR::enum_warning_level level,
+ const char* msg,
+ MYSQL_ERROR ** cond_hdl);
+
+ /**
+ Returns TRUE if one or more ER_NO_SUCH_TABLE errors have been
+ trapped and no other errors have been seen. FALSE otherwise.
+ */
+ bool safely_trapped_errors();
+
+private:
+ int m_handled_errors;
+ int m_unhandled_errors;
+};
+
+
#endif /* SQL_BASE_INCLUDED */
diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc
index f1dddbb2eb5..a5c126a8521 100644
--- a/sql/sql_handler.cc
+++ b/sql/sql_handler.cc
@@ -934,7 +934,7 @@ void mysql_ha_flush(THD *thd)
((hash_tables->table->mdl_ticket &&
hash_tables->table->mdl_ticket->has_pending_conflicting_lock()) ||
(!hash_tables->table->s->tmp_table &&
- hash_tables->table->s->needs_reopen())))
+ hash_tables->table->s->has_old_version())))
mysql_ha_close_table(thd, hash_tables);
}
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index 79a5cfdbbe2..6c23b47997a 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -2705,7 +2705,7 @@ bool Delayed_insert::handle_inserts(void)
thd_proc_info(&thd, "insert");
max_rows= delayed_insert_limit;
- if (thd.killed || table->s->needs_reopen())
+ if (thd.killed || table->s->has_old_version())
{
thd.killed= THD::KILL_CONNECTION;
max_rows= ULONG_MAX; // Do as much as possible
@@ -3592,11 +3592,9 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
*/
if (open_table(thd, create_table, thd->mem_root, &ot_ctx))
{
- mysql_mutex_lock(&LOCK_open);
quick_rm_table(create_info->db_type, create_table->db,
table_case_name(create_info, create_table->table_name),
0);
- mysql_mutex_unlock(&LOCK_open);
}
else
table= create_table->table;
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index b5f55b981c8..b78815f0e52 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -50,6 +50,7 @@
// mysql_backup_table,
// mysql_restore_table
#include "sql_truncate.h" // mysql_truncate_table
+#include "sql_reload.h" // reload_acl_and_cache
#include "sql_admin.h" // mysql_assign_to_keycache
#include "sql_connect.h" // check_user,
// decrease_user_connections,
@@ -1696,140 +1697,6 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident,
/**
- Implementation of FLUSH TABLES <table_list> WITH READ LOCK.
-
- In brief: take exclusive locks, expel tables from the table
- cache, reopen the tables, enter the 'LOCKED TABLES' mode,
- downgrade the locks.
- Note: the function is written to be called from
- mysql_execute_command(), it is not reusable in arbitrary
- execution context.
-
- Required privileges
- -------------------
- Since the statement implicitly enters LOCK TABLES mode,
- it requires LOCK TABLES privilege on every table.
- But since the rest of FLUSH commands require
- the global RELOAD_ACL, it also requires RELOAD_ACL.
-
- Compatibility with the global read lock
- ---------------------------------------
- We don't wait for the GRL, since neither the
- 5.1 combination that this new statement is intended to
- replace (LOCK TABLE <list> WRITE; FLUSH TABLES;),
- nor FLUSH TABLES WITH READ LOCK do.
- @todo: this is not implemented, Dmitry disagrees.
- Currently we wait for GRL in another connection,
- but are compatible with a GRL in our own connection.
-
- Behaviour under LOCK TABLES
- ---------------------------
- Bail out: i.e. don't perform an implicit UNLOCK TABLES.
- This is not consistent with LOCK TABLES statement, but is
- in line with behaviour of FLUSH TABLES WITH READ LOCK, and we
- try to not introduce any new statements with implicit
- semantics.
-
- Compatibility with parallel updates
- -----------------------------------
- As a result, we will wait for all open transactions
- against the tables to complete. After the lock downgrade,
- new transactions will be able to read the tables, but not
- write to them.
-
- Differences from FLUSH TABLES <list>
- -------------------------------------
- - you can't flush WITH READ LOCK a non-existent table
- - you can't flush WITH READ LOCK under LOCK TABLES
- - currently incompatible with the GRL (@todo: fix)
-
- Effect on views and temporary tables.
- ------------------------------------
- You can only apply this command to existing base tables.
- If a view with such name exists, ER_WRONG_OBJECT is returned.
- If a temporary table with such name exists, it's ignored:
- if there is a base table, it's used, otherwise ER_NO_SUCH_TABLE
- is returned.
-*/
-
-static bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables)
-{
- Lock_tables_prelocking_strategy lock_tables_prelocking_strategy;
- TABLE_LIST *table_list;
-
- /*
- This is called from SQLCOM_FLUSH, the transaction has
- been committed implicitly.
- */
-
- /* RELOAD_ACL is checked by the caller. Check table-level privileges. */
- if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables,
- FALSE, UINT_MAX, FALSE))
- goto error;
-
- if (thd->locked_tables_mode)
- {
- my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
- goto error;
- }
-
- /*
- @todo: Since lock_table_names() acquires a global IX
- lock, this actually waits for a GRL in another connection.
- We are thus introducing an incompatibility.
- Do nothing for now, since not taking a global IX violates
- current internal MDL asserts, fix after discussing with
- Dmitry.
- */
- if (lock_table_names(thd, all_tables, 0, thd->variables.lock_wait_timeout,
- MYSQL_OPEN_SKIP_TEMPORARY))
- goto error;
-
- for (table_list= all_tables; table_list;
- table_list= table_list->next_global)
- {
- /* Remove the table from cache. */
- mysql_mutex_lock(&LOCK_open);
- tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
- table_list->db,
- table_list->table_name);
- mysql_mutex_unlock(&LOCK_open);
-
- /* Skip views and temporary tables. */
- table_list->required_type= FRMTYPE_TABLE; /* Don't try to flush views. */
- table_list->open_type= OT_BASE_ONLY; /* Ignore temporary tables. */
- }
-
- if (open_and_lock_tables(thd, all_tables, FALSE,
- MYSQL_OPEN_HAS_MDL_LOCK,
- &lock_tables_prelocking_strategy) ||
- thd->locked_tables_list.init_locked_tables(thd))
- {
- goto error;
- }
- thd->variables.option_bits|= OPTION_TABLE_LOCK;
-
- /*
- Downgrade the exclusive locks.
- Use MDL_SHARED_NO_WRITE as the intended
- post effect of this call is identical
- to LOCK TABLES <...> READ, and we didn't use
- thd->in_lock_talbes and thd->sql_command= SQLCOM_LOCK_TABLES
- hacks to enter the LTM.
- @todo: release the global IX lock here!!!
- */
- for (table_list= all_tables; table_list;
- table_list= table_list->next_global)
- table_list->mdl_request.ticket->downgrade_exclusive_lock(MDL_SHARED_NO_WRITE);
-
- return FALSE;
-
-error:
- return TRUE;
-}
-
-
-/**
Read query from packet and store in thd->query.
Used in COM_QUERY and COM_STMT_PREPARE.
@@ -3226,7 +3093,7 @@ end_with_restore_list:
/* So that DROP TEMPORARY TABLE gets to binlog at commit/rollback */
thd->variables.option_bits|= OPTION_KEEP_LOG;
}
- /* DDL and binlog write order protected by LOCK_open */
+ /* DDL and binlog write order are protected by metadata locks. */
res= mysql_rm_table(thd, first_table, lex->drop_if_exists,
lex->drop_temporary);
}
@@ -3771,6 +3638,10 @@ end_with_restore_list:
if (first_table && lex->type & REFRESH_READ_LOCK)
{
+ /* Check table-level privileges. */
+ if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables,
+ FALSE, UINT_MAX, FALSE))
+ goto error;
if (flush_tables_with_read_lock(thd, all_tables))
goto error;
my_ok(thd);
@@ -6512,258 +6383,6 @@ void add_join_natural(TABLE_LIST *a, TABLE_LIST *b, List<String> *using_fields,
/**
- Reload/resets privileges and the different caches.
-
- @param thd Thread handler (can be NULL!)
- @param options What should be reset/reloaded (tables, privileges, slave...)
- @param tables Tables to flush (if any)
- @param write_to_binlog True if we can write to the binlog.
-
- @note Depending on 'options', it may be very bad to write the
- query to the binlog (e.g. FLUSH SLAVE); this is a
- pointer where reload_acl_and_cache() will put 0 if
- it thinks we really should not write to the binlog.
- Otherwise it will put 1.
-
- @return Error status code
- @retval 0 Ok
- @retval !=0 Error; thd->killed is set or thd->is_error() is true
-*/
-
-bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
- bool *write_to_binlog)
-{
- bool result=0;
- select_errors=0; /* Write if more errors */
- bool tmp_write_to_binlog= 1;
-
- DBUG_ASSERT(!thd || !thd->in_sub_stmt);
-
-#ifndef NO_EMBEDDED_ACCESS_CHECKS
- if (options & REFRESH_GRANT)
- {
- THD *tmp_thd= 0;
- /*
- If reload_acl_and_cache() is called from SIGHUP handler we have to
- allocate temporary THD for execution of acl_reload()/grant_reload().
- */
- if (!thd && (thd= (tmp_thd= new THD)))
- {
- thd->thread_stack= (char*) &tmp_thd;
- thd->store_globals();
- }
-
- if (thd)
- {
- bool reload_acl_failed= acl_reload(thd);
- bool reload_grants_failed= grant_reload(thd);
- bool reload_servers_failed= servers_reload(thd);
-
- if (reload_acl_failed || reload_grants_failed || reload_servers_failed)
- {
- result= 1;
- /*
- When an error is returned, my_message may have not been called and
- the client will hang waiting for a response.
- */
- my_error(ER_UNKNOWN_ERROR, MYF(0), "FLUSH PRIVILEGES failed");
- }
- }
-
- if (tmp_thd)
- {
- delete tmp_thd;
- /* Remember that we don't have a THD */
- my_pthread_setspecific_ptr(THR_THD, 0);
- thd= 0;
- }
- reset_mqh((LEX_USER *)NULL, TRUE);
- }
-#endif
- if (options & REFRESH_LOG)
- {
- /*
- Flush the normal query log, the update log, the binary log,
- the slow query log, the relay log (if it exists) and the log
- tables.
- */
-
- options|= REFRESH_BINARY_LOG;
- options|= REFRESH_RELAY_LOG;
- options|= REFRESH_SLOW_LOG;
- options|= REFRESH_GENERAL_LOG;
- options|= REFRESH_ENGINE_LOG;
- options|= REFRESH_ERROR_LOG;
- }
-
- if (options & REFRESH_ERROR_LOG)
- if (flush_error_log())
- result= 1;
-
- if ((options & REFRESH_SLOW_LOG) && opt_slow_log)
- logger.flush_slow_log();
-
- if ((options & REFRESH_GENERAL_LOG) && opt_log)
- logger.flush_general_log();
-
- if (options & REFRESH_ENGINE_LOG)
- if (ha_flush_logs(NULL))
- result= 1;
-
- if (options & REFRESH_BINARY_LOG)
- {
- /*
- Writing this command to the binlog may result in infinite loops
- when doing mysqlbinlog|mysql, and anyway it does not really make
- sense to log it automatically (would cause more trouble to users
- than it would help them)
- */
- tmp_write_to_binlog= 0;
- if (mysql_bin_log.is_open())
- mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE);
- }
- if (options & REFRESH_RELAY_LOG)
- {
-#ifdef HAVE_REPLICATION
- mysql_mutex_lock(&LOCK_active_mi);
- rotate_relay_log(active_mi);
- mysql_mutex_unlock(&LOCK_active_mi);
-#endif
- }
-#ifdef HAVE_QUERY_CACHE
- if (options & REFRESH_QUERY_CACHE_FREE)
- {
- query_cache.pack(); // FLUSH QUERY CACHE
- options &= ~REFRESH_QUERY_CACHE; // Don't flush cache, just free memory
- }
- if (options & (REFRESH_TABLES | REFRESH_QUERY_CACHE))
- {
- query_cache.flush(); // RESET QUERY CACHE
- }
-#endif /*HAVE_QUERY_CACHE*/
-
- DBUG_ASSERT(!thd || thd->locked_tables_mode ||
- !thd->mdl_context.has_locks() ||
- thd->handler_tables_hash.records ||
- thd->global_read_lock.is_acquired());
-
- /*
- Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too
- (see sql_yacc.yy)
- */
- if (options & (REFRESH_TABLES | REFRESH_READ_LOCK))
- {
- if ((options & REFRESH_READ_LOCK) && thd)
- {
- /*
- On the first hand we need write lock on the tables to be flushed,
- on the other hand we must not try to aspire a global read lock
- if we have a write locked table as this would lead to a deadlock
- when trying to reopen (and re-lock) the table after the flush.
- */
- if (thd->locked_tables_mode)
- {
- my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
- return 1;
- }
- /*
- Writing to the binlog could cause deadlocks, as we don't log
- UNLOCK TABLES
- */
- tmp_write_to_binlog= 0;
- if (thd->global_read_lock.lock_global_read_lock(thd))
- return 1; // Killed
- if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
- FALSE : TRUE))
- result= 1;
-
- if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // Killed
- {
- /* Don't leave things in a half-locked state */
- thd->global_read_lock.unlock_global_read_lock(thd);
- return 1;
- }
- }
- else
- {
- if (thd && thd->locked_tables_mode)
- {
- /*
- If we are under LOCK TABLES we should have a write
- lock on tables which we are going to flush.
- */
- if (tables)
- {
- for (TABLE_LIST *t= tables; t; t= t->next_local)
- if (!find_table_for_mdl_upgrade(thd->open_tables, t->db,
- t->table_name, FALSE))
- return 1;
- }
- else
- {
- for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
- {
- if (! tab->mdl_ticket->is_upgradable_or_exclusive())
- {
- my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
- tab->s->table_name.str);
- return 1;
- }
- }
- }
- }
-
- if (close_cached_tables(thd, tables, FALSE, (options & REFRESH_FAST) ?
- FALSE : TRUE))
- result= 1;
- }
- my_dbopt_cleanup();
- }
- if (options & REFRESH_HOSTS)
- hostname_cache_refresh();
- if (thd && (options & REFRESH_STATUS))
- refresh_status(thd);
- if (options & REFRESH_THREADS)
- flush_thread_cache();
-#ifdef HAVE_REPLICATION
- if (options & REFRESH_MASTER)
- {
- DBUG_ASSERT(thd);
- tmp_write_to_binlog= 0;
- if (reset_master(thd))
- {
- result=1;
- }
- }
-#endif
-#ifdef OPENSSL
- if (options & REFRESH_DES_KEY_FILE)
- {
- if (des_key_file && load_des_key_file(des_key_file))
- result= 1;
- }
-#endif
-#ifdef HAVE_REPLICATION
- if (options & REFRESH_SLAVE)
- {
- tmp_write_to_binlog= 0;
- mysql_mutex_lock(&LOCK_active_mi);
- if (reset_slave(thd, active_mi))
- result=1;
- mysql_mutex_unlock(&LOCK_active_mi);
- }
-#endif
- if (options & REFRESH_USER_RESOURCES)
- reset_mqh((LEX_USER *) NULL, 0); /* purecov: inspected */
- *write_to_binlog= tmp_write_to_binlog;
- /*
- If the query was killed then this function must fail.
- */
- return result || (thd ? thd->killed : 0);
-}
-
-
-/**
kill on thread.
@param thd Thread class
diff --git a/sql/sql_parse.h b/sql/sql_parse.h
index dd5b8aacc0a..ad620544d29 100644
--- a/sql/sql_parse.h
+++ b/sql/sql_parse.h
@@ -93,8 +93,6 @@ void mysql_init_multi_delete(LEX *lex);
bool multi_delete_set_locks_and_link_aux_tables(LEX *lex);
void create_table_set_open_action_and_adjust_tables(LEX *lex);
pthread_handler_t handle_bootstrap(void *arg);
-bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables,
- bool *write_to_binlog);
int mysql_execute_command(THD *thd);
bool do_command(THD *thd);
void do_handle_bootstrap(THD *thd);
diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc
index 0107a4d8144..5bbe946179e 100644
--- a/sql/sql_partition.cc
+++ b/sql/sql_partition.cc
@@ -6342,7 +6342,7 @@ static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt)
*/
tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED,
table->s->db.str,
- table->s->table_name.str);
+ table->s->table_name.str, TRUE);
}
}
mysql_mutex_unlock(&LOCK_open);
diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc
index e3323260373..0020741b78f 100644
--- a/sql/sql_plugin.cc
+++ b/sql/sql_plugin.cc
@@ -1393,8 +1393,9 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv)
READ_RECORD read_record_info;
int error;
THD *new_thd= &thd;
+ bool result;
#ifdef EMBEDDED_LIBRARY
- bool table_exists;
+ No_such_table_error_handler error_handler;
#endif /* EMBEDDED_LIBRARY */
DBUG_ENTER("plugin_load");
@@ -1410,13 +1411,18 @@ static void plugin_load(MEM_ROOT *tmp_root, int *argc, char **argv)
When building an embedded library, if the mysql.plugin table
does not exist, we silently ignore the missing table
*/
- if (check_if_table_exists(new_thd, &tables, &table_exists))
- table_exists= FALSE;
- if (!table_exists)
+ new_thd->push_internal_handler(&error_handler);
+#endif /* EMBEDDED_LIBRARY */
+
+ result= open_and_lock_tables(new_thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT);
+
+#ifdef EMBEDDED_LIBRARY
+ new_thd->pop_internal_handler();
+ if (error_handler.safely_trapped_errors())
goto end;
#endif /* EMBEDDED_LIBRARY */
- if (open_and_lock_tables(new_thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
+ if (result)
{
DBUG_PRINT("error",("Can't open plugin table"));
sql_print_error("Can't open the mysql.plugin table. Please "
diff --git a/sql/sql_reload.cc b/sql/sql_reload.cc
new file mode 100644
index 00000000000..bf38af78536
--- /dev/null
+++ b/sql/sql_reload.cc
@@ -0,0 +1,427 @@
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+#include "sql_reload.h"
+#include "sql_priv.h"
+#include "mysqld.h" // select_errors
+#include "sql_class.h" // THD
+#include "sql_acl.h" // acl_reload
+#include "sql_servers.h" // servers_reload
+#include "sql_connect.h" // reset_mqh
+#include "sql_base.h" // close_cached_tables
+#include "sql_db.h" // my_dbopt_cleanup
+#include "hostname.h" // hostname_cache_refresh
+#include "sql_repl.h" // reset_master, reset_slave
+#include "debug_sync.h"
+
+
+/**
+ Reload/resets privileges and the different caches.
+
+ @param thd Thread handler (can be NULL!)
+ @param options What should be reset/reloaded (tables, privileges, slave...)
+ @param tables Tables to flush (if any)
+ @param write_to_binlog True if we can write to the binlog.
+
+ @note Depending on 'options', it may be very bad to write the
+ query to the binlog (e.g. FLUSH SLAVE); this is a
+ pointer where reload_acl_and_cache() will put 0 if
+ it thinks we really should not write to the binlog.
+ Otherwise it will put 1.
+
+ @return Error status code
+ @retval 0 Ok
+ @retval !=0 Error; thd->killed is set or thd->is_error() is true
+*/
+
+bool reload_acl_and_cache(THD *thd, unsigned long options,
+ TABLE_LIST *tables, bool *write_to_binlog)
+{
+ bool result=0;
+ select_errors=0; /* Write if more errors */
+ bool tmp_write_to_binlog= 1;
+
+ DBUG_ASSERT(!thd || !thd->in_sub_stmt);
+
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ if (options & REFRESH_GRANT)
+ {
+ THD *tmp_thd= 0;
+ /*
+ If reload_acl_and_cache() is called from SIGHUP handler we have to
+ allocate temporary THD for execution of acl_reload()/grant_reload().
+ */
+ if (!thd && (thd= (tmp_thd= new THD)))
+ {
+ thd->thread_stack= (char*) &tmp_thd;
+ thd->store_globals();
+ }
+
+ if (thd)
+ {
+ bool reload_acl_failed= acl_reload(thd);
+ bool reload_grants_failed= grant_reload(thd);
+ bool reload_servers_failed= servers_reload(thd);
+
+ if (reload_acl_failed || reload_grants_failed || reload_servers_failed)
+ {
+ result= 1;
+ /*
+ When an error is returned, my_message may have not been called and
+ the client will hang waiting for a response.
+ */
+ my_error(ER_UNKNOWN_ERROR, MYF(0), "FLUSH PRIVILEGES failed");
+ }
+ }
+
+ if (tmp_thd)
+ {
+ delete tmp_thd;
+ /* Remember that we don't have a THD */
+ my_pthread_setspecific_ptr(THR_THD, 0);
+ thd= 0;
+ }
+ reset_mqh((LEX_USER *)NULL, TRUE);
+ }
+#endif
+ if (options & REFRESH_LOG)
+ {
+ /*
+ Flush the normal query log, the update log, the binary log,
+ the slow query log, the relay log (if it exists) and the log
+ tables.
+ */
+
+ options|= REFRESH_BINARY_LOG;
+ options|= REFRESH_RELAY_LOG;
+ options|= REFRESH_SLOW_LOG;
+ options|= REFRESH_GENERAL_LOG;
+ options|= REFRESH_ENGINE_LOG;
+ options|= REFRESH_ERROR_LOG;
+ }
+
+ if (options & REFRESH_ERROR_LOG)
+ if (flush_error_log())
+ result= 1;
+
+ if ((options & REFRESH_SLOW_LOG) && opt_slow_log)
+ logger.flush_slow_log();
+
+ if ((options & REFRESH_GENERAL_LOG) && opt_log)
+ logger.flush_general_log();
+
+ if (options & REFRESH_ENGINE_LOG)
+ if (ha_flush_logs(NULL))
+ result= 1;
+
+ if (options & REFRESH_BINARY_LOG)
+ {
+ /*
+ Writing this command to the binlog may result in infinite loops
+ when doing mysqlbinlog|mysql, and anyway it does not really make
+ sense to log it automatically (would cause more trouble to users
+ than it would help them)
+ */
+ tmp_write_to_binlog= 0;
+ if (mysql_bin_log.is_open())
+ mysql_bin_log.rotate_and_purge(RP_FORCE_ROTATE);
+ }
+ if (options & REFRESH_RELAY_LOG)
+ {
+#ifdef HAVE_REPLICATION
+ mysql_mutex_lock(&LOCK_active_mi);
+ rotate_relay_log(active_mi);
+ mysql_mutex_unlock(&LOCK_active_mi);
+#endif
+ }
+#ifdef HAVE_QUERY_CACHE
+ if (options & REFRESH_QUERY_CACHE_FREE)
+ {
+ query_cache.pack(); // FLUSH QUERY CACHE
+ options &= ~REFRESH_QUERY_CACHE; // Don't flush cache, just free memory
+ }
+ if (options & (REFRESH_TABLES | REFRESH_QUERY_CACHE))
+ {
+ query_cache.flush(); // RESET QUERY CACHE
+ }
+#endif /*HAVE_QUERY_CACHE*/
+
+ DBUG_ASSERT(!thd || thd->locked_tables_mode ||
+ !thd->mdl_context.has_locks() ||
+ thd->handler_tables_hash.records ||
+ thd->global_read_lock.is_acquired());
+
+ /*
+ Note that if REFRESH_READ_LOCK bit is set then REFRESH_TABLES is set too
+ (see sql_yacc.yy)
+ */
+ if (options & (REFRESH_TABLES | REFRESH_READ_LOCK))
+ {
+ if ((options & REFRESH_READ_LOCK) && thd)
+ {
+ /*
+ On the first hand we need write lock on the tables to be flushed,
+ on the other hand we must not try to aspire a global read lock
+ if we have a write locked table as this would lead to a deadlock
+ when trying to reopen (and re-lock) the table after the flush.
+ */
+ if (thd->locked_tables_mode)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ return 1;
+ }
+ /*
+ Writing to the binlog could cause deadlocks, as we don't log
+ UNLOCK TABLES
+ */
+ tmp_write_to_binlog= 0;
+ if (thd->global_read_lock.lock_global_read_lock(thd))
+ return 1; // Killed
+ if (close_cached_tables(thd, tables,
+ ((options & REFRESH_FAST) ? FALSE : TRUE),
+ thd->variables.lock_wait_timeout))
+ result= 1;
+
+ if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // Killed
+ {
+ /* Don't leave things in a half-locked state */
+ thd->global_read_lock.unlock_global_read_lock(thd);
+ return 1;
+ }
+ }
+ else
+ {
+ if (thd && thd->locked_tables_mode)
+ {
+ /*
+ If we are under LOCK TABLES we should have a write
+ lock on tables which we are going to flush.
+ */
+ if (tables)
+ {
+ for (TABLE_LIST *t= tables; t; t= t->next_local)
+ if (!find_table_for_mdl_upgrade(thd->open_tables, t->db,
+ t->table_name, FALSE))
+ return 1;
+ }
+ else
+ {
+ for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
+ {
+ if (! tab->mdl_ticket->is_upgradable_or_exclusive())
+ {
+ my_error(ER_TABLE_NOT_LOCKED_FOR_WRITE, MYF(0),
+ tab->s->table_name.str);
+ return 1;
+ }
+ }
+ }
+ }
+
+ if (close_cached_tables(thd, tables,
+ ((options & REFRESH_FAST) ? FALSE : TRUE),
+ (thd ? thd->variables.lock_wait_timeout :
+ LONG_TIMEOUT)))
+ result= 1;
+ }
+ my_dbopt_cleanup();
+ }
+ if (options & REFRESH_HOSTS)
+ hostname_cache_refresh();
+ if (thd && (options & REFRESH_STATUS))
+ refresh_status(thd);
+ if (options & REFRESH_THREADS)
+ flush_thread_cache();
+#ifdef HAVE_REPLICATION
+ if (options & REFRESH_MASTER)
+ {
+ DBUG_ASSERT(thd);
+ tmp_write_to_binlog= 0;
+ if (reset_master(thd))
+ {
+ result=1;
+ }
+ }
+#endif
+#ifdef OPENSSL
+ if (options & REFRESH_DES_KEY_FILE)
+ {
+ if (des_key_file && load_des_key_file(des_key_file))
+ result= 1;
+ }
+#endif
+#ifdef HAVE_REPLICATION
+ if (options & REFRESH_SLAVE)
+ {
+ tmp_write_to_binlog= 0;
+ mysql_mutex_lock(&LOCK_active_mi);
+ if (reset_slave(thd, active_mi))
+ result=1;
+ mysql_mutex_unlock(&LOCK_active_mi);
+ }
+#endif
+ if (options & REFRESH_USER_RESOURCES)
+ reset_mqh((LEX_USER *) NULL, 0); /* purecov: inspected */
+ *write_to_binlog= tmp_write_to_binlog;
+ /*
+ If the query was killed then this function must fail.
+ */
+ return result || (thd ? thd->killed : 0);
+}
+
+
+/**
+ Implementation of FLUSH TABLES <table_list> WITH READ LOCK.
+
+ In brief: take exclusive locks, expel tables from the table
+ cache, reopen the tables, enter the 'LOCKED TABLES' mode,
+ downgrade the locks.
+ Note: the function is written to be called from
+ mysql_execute_command(), it is not reusable in arbitrary
+ execution context.
+
+ Required privileges
+ -------------------
+ Since the statement implicitly enters LOCK TABLES mode,
+ it requires LOCK TABLES privilege on every table.
+ But since the rest of FLUSH commands require
+ the global RELOAD_ACL, it also requires RELOAD_ACL.
+
+ Compatibility with the global read lock
+ ---------------------------------------
+ We don't wait for the GRL, since neither the
+ 5.1 combination that this new statement is intended to
+ replace (LOCK TABLE <list> WRITE; FLUSH TABLES;),
+ nor FLUSH TABLES WITH READ LOCK do.
+ @todo: this is not implemented, Dmitry disagrees.
+ Currently we wait for GRL in another connection,
+ but are compatible with a GRL in our own connection.
+
+ Behaviour under LOCK TABLES
+ ---------------------------
+ Bail out: i.e. don't perform an implicit UNLOCK TABLES.
+ This is not consistent with LOCK TABLES statement, but is
+ in line with behaviour of FLUSH TABLES WITH READ LOCK, and we
+ try to not introduce any new statements with implicit
+ semantics.
+
+ Compatibility with parallel updates
+ -----------------------------------
+ As a result, we will wait for all open transactions
+ against the tables to complete. After the lock downgrade,
+ new transactions will be able to read the tables, but not
+ write to them.
+
+ Differences from FLUSH TABLES <list>
+ -------------------------------------
+ - you can't flush WITH READ LOCK a non-existent table
+ - you can't flush WITH READ LOCK under LOCK TABLES
+ - currently incompatible with the GRL (@todo: fix)
+
+ Effect on views and temporary tables.
+ ------------------------------------
+ You can only apply this command to existing base tables.
+ If a view with such name exists, ER_WRONG_OBJECT is returned.
+ If a temporary table with such name exists, it's ignored:
+ if there is a base table, it's used, otherwise ER_NO_SUCH_TABLE
+ is returned.
+
+ Implicit commit
+ ---------------
+ This statement causes an implicit commit before and
+ after it.
+
+ HANDLER SQL
+ -----------
+ If this connection has HANDLERs open against
+ some of the tables being FLUSHed, these handlers
+ are implicitly flushed (lose their position).
+*/
+
+bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables)
+{
+ Lock_tables_prelocking_strategy lock_tables_prelocking_strategy;
+ TABLE_LIST *table_list;
+ MDL_request_list mdl_requests;
+
+ /*
+ This is called from SQLCOM_FLUSH, the transaction has
+ been committed implicitly.
+ */
+
+ if (thd->locked_tables_mode)
+ {
+ my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
+ goto error;
+ }
+
+ /*
+ Acquire SNW locks on tables to be flushed. We can't use
+ lock_table_names() here as this call will also acquire global IX
+ and database-scope IX locks on the tables, and this will make
+ this statement incompatible with FLUSH TABLES WITH READ LOCK.
+ */
+ for (table_list= all_tables; table_list;
+ table_list= table_list->next_global)
+ mdl_requests.push_front(&table_list->mdl_request);
+
+ if (thd->mdl_context.acquire_locks(&mdl_requests,
+ thd->variables.lock_wait_timeout))
+ goto error;
+
+ DEBUG_SYNC(thd,"flush_tables_with_read_lock_after_acquire_locks");
+
+ for (table_list= all_tables; table_list;
+ table_list= table_list->next_global)
+ {
+ /* Request removal of table from cache. */
+ tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED,
+ table_list->db,
+ table_list->table_name, FALSE);
+
+ /* Skip views and temporary tables. */
+ table_list->required_type= FRMTYPE_TABLE; /* Don't try to flush views. */
+ table_list->open_type= OT_BASE_ONLY; /* Ignore temporary tables. */
+ }
+
+ /*
+ Before opening and locking tables the below call also waits
+ for old shares to go away, so the fact that we don't pass
+ MYSQL_LOCK_IGNORE_FLUSH flag to it is important.
+ */
+ if (open_and_lock_tables(thd, all_tables, FALSE,
+ MYSQL_OPEN_HAS_MDL_LOCK,
+ &lock_tables_prelocking_strategy) ||
+ thd->locked_tables_list.init_locked_tables(thd))
+ {
+ goto error;
+ }
+ thd->variables.option_bits|= OPTION_TABLE_LOCK;
+
+ /*
+ We don't downgrade MDL_SHARED_NO_WRITE here as the intended
+ post effect of this call is identical to LOCK TABLES <...> READ,
+ and we didn't use thd->in_lock_talbes and
+ thd->sql_command= SQLCOM_LOCK_TABLES hacks to enter the LTM.
+ */
+
+ return FALSE;
+
+error:
+ return TRUE;
+}
+
+
+
diff --git a/sql/sql_reload.h b/sql/sql_reload.h
new file mode 100644
index 00000000000..0df5485c907
--- /dev/null
+++ b/sql/sql_reload.h
@@ -0,0 +1,26 @@
+#ifndef SQL_RELOAD_INCLUDED
+#define SQL_RELOAD_INCLUDED
+/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
+
+class THD;
+struct TABLE_LIST;
+
+bool reload_acl_and_cache(THD *thd, unsigned long options,
+ TABLE_LIST *tables, bool *write_to_binlog);
+
+bool flush_tables_with_read_lock(THD *thd, TABLE_LIST *all_tables);
+
+#endif
diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc
index 301b22bd70e..97f8e46d052 100644
--- a/sql/sql_rename.cc
+++ b/sql/sql_rename.cc
@@ -147,13 +147,15 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
MYSQL_OPEN_SKIP_TEMPORARY))
goto err;
- mysql_mutex_lock(&LOCK_open);
-
for (ren_table= table_list; ren_table; ren_table= ren_table->next_local)
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, ren_table->db,
- ren_table->table_name);
+ ren_table->table_name, FALSE);
error=0;
+ /*
+ An exclusive lock on table names is satisfactory to ensure
+ no other thread accesses this table.
+ */
if ((ren_table=rename_tables(thd,table_list,0)))
{
/* Rename didn't succeed; rename back the tables in reverse order */
@@ -175,17 +177,6 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
error= 1;
}
- /*
- An exclusive lock on table names is satisfactory to ensure
- no other thread accesses this table.
- However, NDB assumes that handler::rename_tables is called under
- LOCK_open. And it indeed is, from ALTER TABLE.
- TODO: remove this limitation.
- We still should unlock LOCK_open as early as possible, to provide
- higher concurrency - query_cache_invalidate can take minutes to
- complete.
- */
- mysql_mutex_unlock(&LOCK_open);
if (!silent && !error)
{
@@ -197,8 +188,6 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent)
if (!error)
query_cache_invalidate3(thd, table_list, 0);
- thd->mdl_context.release_transactional_locks();
-
err:
thd->global_read_lock.start_waiting_global_read_lock(thd);
DBUG_RETURN(error || binlog_error);
diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc
index cfbf8e96719..bc845ed2cdd 100644
--- a/sql/sql_servers.cc
+++ b/sql/sql_servers.cc
@@ -628,7 +628,7 @@ int drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
/* close the servers table before we call closed_cached_connection_tables */
close_mysql_tables(thd);
- if (close_cached_connection_tables(thd, TRUE, &name))
+ if (close_cached_connection_tables(thd, &name))
{
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_UNKNOWN_ERROR, "Server connection in use");
@@ -1057,7 +1057,7 @@ int alter_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
/* close the servers table before we call closed_cached_connection_tables */
close_mysql_tables(thd);
- if (close_cached_connection_tables(thd, FALSE, &name))
+ if (close_cached_connection_tables(thd, &name))
{
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_UNKNOWN_ERROR, "Server connection in use");
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index 4d58db2e36c..3f0c0b9eaf9 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -3273,8 +3273,8 @@ static int fill_schema_table_from_frm(THD *thd, TABLE_LIST *tables,
/*
TODO: investigate if in this particular situation we can get by
- simply obtaining internal lock of data-dictionary (ATM it
- is LOCK_open) instead of obtaning full-blown metadata lock.
+ simply obtaining internal lock of the data-dictionary
+ instead of obtaining full-blown metadata lock.
*/
if (try_acquire_high_prio_shared_mdl_lock(thd, &table_list, can_deadlock))
{
@@ -7657,7 +7657,7 @@ static bool show_create_trigger_impl(THD *thd,
*/
static
-TABLE_LIST *get_trigger_table_impl(THD *thd, const sp_name *trg_name)
+TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name)
{
char trn_path_buff[FN_REFLEN];
LEX_STRING trn_path= { trn_path_buff, 0 };
@@ -7694,39 +7694,6 @@ TABLE_LIST *get_trigger_table_impl(THD *thd, const sp_name *trg_name)
return table;
}
-/**
- Read TRN and TRG files to obtain base table name for the specified
- trigger name and construct TABE_LIST object for the base table. Acquire
- LOCK_open when doing this.
-
- @param thd Thread context.
- @param trg_name Trigger name.
-
- @return TABLE_LIST object corresponding to the base table.
-*/
-
-static
-TABLE_LIST *get_trigger_table(THD *thd, const sp_name *trg_name)
-{
- /* Acquire LOCK_open (stop the server). */
-
- mysql_mutex_lock(&LOCK_open);
-
- /*
- Load base table name from the TRN-file and create TABLE_LIST object.
- */
-
- TABLE_LIST *lst= get_trigger_table_impl(thd, trg_name);
-
- /* Release LOCK_open (continue the server). */
-
- mysql_mutex_unlock(&LOCK_open);
-
- /* That's it. */
-
- return lst;
-}
-
/**
SHOW CREATE TRIGGER high-level implementation.
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index de492a14c78..bd5f381b8b8 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -1719,7 +1719,6 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags)
completing this we write a new phase to the log entry that will
deactivate it.
*/
- mysql_mutex_lock(&LOCK_open);
if (mysql_file_delete(key_file_frm, frm_name, MYF(MY_WME)) ||
#ifdef WITH_PARTITION_STORAGE_ENGINE
lpt->table->file->ha_create_handler_files(path, shadow_path,
@@ -1775,12 +1774,12 @@ bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags)
#endif
err:
- mysql_mutex_unlock(&LOCK_open);
#ifdef WITH_PARTITION_STORAGE_ENGINE
deactivate_ddl_log_entry(part_info->frm_log_entry->entry_pos);
part_info->frm_log_entry= NULL;
(void) sync_ddl_log();
#endif
+ ;
}
end:
@@ -1999,10 +1998,11 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
if (lock_table_names(thd, tables, NULL, thd->variables.lock_wait_timeout,
MYSQL_OPEN_SKIP_TEMPORARY))
DBUG_RETURN(1);
- mysql_mutex_lock(&LOCK_open);
for (table= tables; table; table= table->next_local)
- tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name);
- mysql_mutex_unlock(&LOCK_open);
+ {
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name,
+ FALSE);
+ }
}
else
{
@@ -2171,15 +2171,9 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
built_query.append("`,");
}
}
-
- /*
- TODO: Investigate what should be done to remove this lock completely.
- Is exclusive meta-data lock enough ?
- */
DEBUG_SYNC(thd, "rm_table_part2_before_delete_table");
DBUG_EXECUTE_IF("sleep_before_part2_delete_table",
my_sleep(100000););
- mysql_mutex_lock(&LOCK_open);
error= 0;
if (drop_temporary ||
((access(path, F_OK) &&
@@ -2209,8 +2203,7 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
char *end;
/*
Cannot use the db_type from the table, since that might have changed
- while waiting for the exclusive name lock. We are under LOCK_open,
- so reading from the frm-file is safe.
+ while waiting for the exclusive name lock.
*/
if (frm_db_type == DB_TYPE_UNKNOWN)
{
@@ -2252,7 +2245,6 @@ int mysql_rm_table_part2(THD *thd, TABLE_LIST *tables, bool if_exists,
}
non_tmp_error= error ? TRUE : non_tmp_error;
}
- mysql_mutex_unlock(&LOCK_open);
if (error)
{
if (wrong_tables.length())
@@ -4098,7 +4090,6 @@ bool mysql_create_table_no_lock(THD *thd,
goto err;
}
- mysql_mutex_lock(&LOCK_open);
if (!internal_tmp_table && !(create_info->options & HA_LEX_CREATE_TMP_TABLE))
{
if (!access(path,F_OK))
@@ -4106,7 +4097,7 @@ bool mysql_create_table_no_lock(THD *thd,
if (create_info->options & HA_LEX_CREATE_IF_NOT_EXISTS)
goto warn;
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
- goto unlock_and_end;
+ goto err;
}
/*
We don't assert here, but check the result, because the table could be
@@ -4116,11 +4107,14 @@ bool mysql_create_table_no_lock(THD *thd,
Then she could create the table. This case is pretty obscure and
therefore we don't introduce a new error message only for it.
*/
+ mysql_mutex_lock(&LOCK_open);
if (get_cached_table_share(db, table_name))
{
+ mysql_mutex_unlock(&LOCK_open);
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name);
- goto unlock_and_end;
+ goto err;
}
+ mysql_mutex_unlock(&LOCK_open);
}
/*
@@ -4128,7 +4122,7 @@ bool mysql_create_table_no_lock(THD *thd,
exist in any storage engine. In such a case it should
be discovered and the error ER_TABLE_EXISTS_ERROR be returned
unless user specified CREATE TABLE IF EXISTS
- The LOCK_open mutex has been locked to make sure no
+ An exclusive metadata lock ensures that no
one else is attempting to discover the table. Since
it's not on disk as a frm file, no one could be using it!
*/
@@ -4149,12 +4143,12 @@ bool mysql_create_table_no_lock(THD *thd,
if (create_if_not_exists)
goto warn;
my_error(ER_TABLE_EXISTS_ERROR,MYF(0),table_name);
- goto unlock_and_end;
+ goto err;
break;
default:
DBUG_PRINT("info", ("error: %u from storage engine", retcode));
my_error(retcode, MYF(0),table_name);
- goto unlock_and_end;
+ goto err;
}
}
@@ -4186,7 +4180,7 @@ bool mysql_create_table_no_lock(THD *thd,
if (test_if_data_home_dir(dirpath))
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), "DATA DIRECTORY");
- goto unlock_and_end;
+ goto err;
}
}
if (create_info->index_file_name)
@@ -4195,7 +4189,7 @@ bool mysql_create_table_no_lock(THD *thd,
if (test_if_data_home_dir(dirpath))
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), "INDEX DIRECTORY");
- goto unlock_and_end;
+ goto err;
}
}
}
@@ -4203,7 +4197,7 @@ bool mysql_create_table_no_lock(THD *thd,
#ifdef WITH_PARTITION_STORAGE_ENGINE
if (check_partition_dirs(thd->lex->part_info))
{
- goto unlock_and_end;
+ goto err;
}
#endif /* WITH_PARTITION_STORAGE_ENGINE */
@@ -4226,7 +4220,7 @@ bool mysql_create_table_no_lock(THD *thd,
if (rea_create_table(thd, path, db, table_name,
create_info, alter_info->create_list,
key_count, key_info_buffer, file))
- goto unlock_and_end;
+ goto err;
if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
{
@@ -4235,7 +4229,7 @@ bool mysql_create_table_no_lock(THD *thd,
if (!(table= open_temporary_table(thd, path, db, table_name, 1)))
{
(void) rm_temporary_table(create_info->db_type, path);
- goto unlock_and_end;
+ goto err;
}
if (is_trans != NULL)
@@ -4245,9 +4239,6 @@ bool mysql_create_table_no_lock(THD *thd,
}
error= FALSE;
-unlock_and_end:
- mysql_mutex_unlock(&LOCK_open);
-
err:
thd_proc_info(thd, "After create");
delete file;
@@ -4258,7 +4249,7 @@ warn:
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
alias);
- goto unlock_and_end;
+ goto err;
}
@@ -5920,7 +5911,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
else
{
*fn_ext(new_name)=0;
- mysql_mutex_lock(&LOCK_open);
if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias, 0))
error= -1;
else if (Table_triggers_list::change_table_name(thd, db, table_name,
@@ -5930,7 +5920,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
table_name, 0);
error= -1;
}
- mysql_mutex_unlock(&LOCK_open);
}
}
@@ -6549,7 +6538,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
/* This type cannot happen in regular ALTER. */
new_db_type= old_db_type= NULL;
}
- mysql_mutex_lock(&LOCK_open);
if (mysql_rename_table(old_db_type, db, table_name, db, old_name,
FN_TO_IS_TMP))
{
@@ -6576,8 +6564,6 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
if (! error)
(void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP);
- mysql_mutex_unlock(&LOCK_open);
-
if (error)
{
/* This shouldn't happen. But let us play it safe. */
@@ -7192,4 +7178,3 @@ static bool check_engine(THD *thd, const char *table_name,
}
return FALSE;
}
-
diff --git a/sql/sql_test.cc b/sql/sql_test.cc
index 501c4cf6a94..9d0614f8529 100644
--- a/sql/sql_test.cc
+++ b/sql/sql_test.cc
@@ -118,7 +118,7 @@ static void print_cached_tables(void)
printf("unused_links isn't linked properly\n");
return;
}
- } while (count++ < table_cache_count && (lnk=lnk->next) != start_link);
+ } while (count++ < cached_open_tables() && (lnk=lnk->next) != start_link);
if (lnk != start_link)
{
printf("Unused_links aren't connected\n");
@@ -416,7 +416,7 @@ static void display_table_locks(void)
void *saved_base;
DYNAMIC_ARRAY saved_table_locks;
- (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), table_cache_count + 20,50);
+ (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), cached_open_tables() + 20,50);
mysql_mutex_lock(&THR_LOCK_lock);
for (list= thr_lock_thread_list; list; list= list_rest(list))
{
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
index a5664b00287..b81461e8371 100644
--- a/sql/sql_trigger.cc
+++ b/sql/sql_trigger.cc
@@ -394,9 +394,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
/*
We don't want perform our operations while global read lock is held
so we have to wait until its end and then prevent it from occurring
- again until we are done, unless we are under lock tables. (Acquiring
- LOCK_open is not enough because global read lock is held without holding
- LOCK_open).
+ again until we are done, unless we are under lock tables.
*/
if (!thd->locked_tables_mode &&
thd->global_read_lock.wait_if_global_read_lock(thd, FALSE, TRUE))
@@ -516,11 +514,9 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
goto end;
}
- mysql_mutex_lock(&LOCK_open);
result= (create ?
table->triggers->create_trigger(thd, tables, &stmt_query):
table->triggers->drop_trigger(thd, tables, &stmt_query));
- mysql_mutex_unlock(&LOCK_open);
if (result)
goto end;
@@ -1680,9 +1676,6 @@ bool add_table_for_trigger(THD *thd,
@param db schema for table
@param name name for table
- @note
- The calling thread should hold the LOCK_open mutex;
-
@retval
False success
@retval
@@ -1912,14 +1905,10 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db,
/*
This method interfaces the mysql server code protected by
- either LOCK_open mutex or with an exclusive metadata lock.
- In the future, only an exclusive metadata lock will be enough.
+ an exclusive metadata lock.
*/
-#ifndef DBUG_OFF
- if (thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, old_table,
- MDL_EXCLUSIVE))
- mysql_mutex_assert_owner(&LOCK_open);
-#endif
+ DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, old_table,
+ MDL_EXCLUSIVE));
DBUG_ASSERT(my_strcasecmp(table_alias_charset, db, new_db) ||
my_strcasecmp(table_alias_charset, old_table, new_table));
diff --git a/sql/sql_truncate.cc b/sql/sql_truncate.cc
index 62e51d9dfe6..a61abdafe3e 100644
--- a/sql/sql_truncate.cc
+++ b/sql/sql_truncate.cc
@@ -328,10 +328,8 @@ static bool open_and_lock_table_for_truncate(THD *thd, TABLE_LIST *table_ref,
upgrade_shared_lock_to_exclusive(table_ref->mdl_request.ticket,
timeout))
DBUG_RETURN(TRUE);
- mysql_mutex_lock(&LOCK_open);
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table_ref->db,
- table_ref->table_name);
- mysql_mutex_unlock(&LOCK_open);
+ table_ref->table_name, FALSE);
}
}
else
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
index 64386d755eb..5fdf7b8d850 100644
--- a/sql/sql_view.cc
+++ b/sql/sql_view.cc
@@ -656,7 +656,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
goto err;
}
- mysql_mutex_lock(&LOCK_open);
res= mysql_register_view(thd, view, mode);
if (mysql_bin_log.is_open())
@@ -703,7 +702,6 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views,
res= TRUE;
}
- mysql_mutex_unlock(&LOCK_open);
if (mode != VIEW_CREATE_NEW)
query_cache_invalidate3(thd, view, 0);
thd->global_read_lock.start_waiting_global_read_lock(thd);
@@ -1654,10 +1652,8 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode)
MYSQL_OPEN_SKIP_TEMPORARY))
DBUG_RETURN(TRUE);
- mysql_mutex_lock(&LOCK_open);
for (view= views; view; view= view->next_local)
{
- TABLE_SHARE *share;
frm_type_enum type= FRMTYPE_ERROR;
build_table_filename(path, sizeof(path) - 1,
view->db, view->table_name, reg_ext, 0);
@@ -1696,16 +1692,12 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode)
some_views_deleted= TRUE;
/*
- For a view, there is only one table_share object which should never
- be used outside of LOCK_open
+ For a view, there is a TABLE_SHARE object, but its
+ ref_count never goes above 1. Remove it from the table
+ definition cache, in case the view was cached.
*/
- if ((share= get_cached_table_share(view->db, view->table_name)))
- {
- DBUG_ASSERT(share->ref_count == 0);
- share->ref_count++;
- share->version= 0;
- release_table_share(share);
- }
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, view->db, view->table_name,
+ FALSE);
query_cache_invalidate3(thd, view, 0);
sp_cache_invalidate();
}
@@ -1730,8 +1722,6 @@ bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode)
something_wrong= 1;
}
- mysql_mutex_unlock(&LOCK_open);
-
if (something_wrong)
{
DBUG_RETURN(TRUE);
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 27510cefad3..6a510c04054 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -11276,9 +11276,8 @@ opt_with_read_lock:
{
TABLE_LIST *tables= Lex->query_tables;
Lex->type|= REFRESH_READ_LOCK;
- /* We acquire an X lock currently and then downgrade. */
for (; tables; tables= tables->next_global)
- tables->mdl_request.set_type(MDL_EXCLUSIVE);
+ tables->mdl_request.set_type(MDL_SHARED_NO_WRITE);
}
;
diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc
index 320e6d9253e..fdda514986b 100644
--- a/sql/sys_vars.cc
+++ b/sql/sys_vars.cc
@@ -1492,7 +1492,8 @@ static bool fix_read_only(sys_var *self, THD *thd, enum_var_type type)
can cause to wait on a read lock, it's required for the client application
to unlock everything, and acceptable for the server to wait on all locks.
*/
- if ((result= close_cached_tables(thd, NULL, FALSE, TRUE)))
+ if ((result= close_cached_tables(thd, NULL, TRUE,
+ thd->variables.lock_wait_timeout)))
goto end_with_read_lock;
if ((result= thd->global_read_lock.make_global_read_lock_block_commit(thd)))
@@ -1616,14 +1617,17 @@ static Sys_var_charptr Sys_socket(
READ_ONLY GLOBAL_VAR(mysqld_unix_port), CMD_LINE(REQUIRED_ARG),
IN_FS_CHARSET, DEFAULT(0));
-#ifdef HAVE_THR_SETCONCURRENCY
+/*
+ thread_concurrency is a no-op on all platforms since
+ MySQL 5.1. It will be removed in the context of
+ WL#5265
+*/
static Sys_var_ulong Sys_thread_concurrency(
"thread_concurrency",
"Permits the application to give the threads system a hint for "
"the desired number of threads that should be run at the same time",
READ_ONLY GLOBAL_VAR(concurrency), CMD_LINE(REQUIRED_ARG),
VALID_RANGE(1, 512), DEFAULT(DEFAULT_CONCURRENCY), BLOCK_SIZE(1));
-#endif
static Sys_var_ulong Sys_thread_stack(
"thread_stack", "The stack size for each thread",
diff --git a/sql/table.cc b/sql/table.cc
index bcdfd23b4c1..f08a0aa84ca 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -34,6 +34,7 @@
#include <m_ctype.h>
#include "my_md5.h"
#include "sql_select.h"
+#include "mdl.h" // MDL_wait_for_graph_visitor
/* INFORMATION_SCHEMA name */
LEX_STRING INFORMATION_SCHEMA_NAME= {C_STRING_WITH_LEN("information_schema")};
@@ -325,6 +326,7 @@ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key,
share->used_tables.empty();
share->free_tables.empty();
+ share->m_flush_tickets.empty();
memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root));
mysql_mutex_init(key_TABLE_SHARE_LOCK_ha_data,
@@ -389,52 +391,92 @@ void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key,
share->used_tables.empty();
share->free_tables.empty();
+ share->m_flush_tickets.empty();
DBUG_VOID_RETURN;
}
+/**
+ Release resources (plugins) used by the share and free its memory.
+ TABLE_SHARE is self-contained -- it's stored in its own MEM_ROOT.
+ Free this MEM_ROOT.
+*/
+
+void TABLE_SHARE::destroy()
+{
+ uint idx;
+ KEY *info_it;
+
+ /* The mutex is initialized only for shares that are part of the TDC */
+ if (tmp_table == NO_TMP_TABLE)
+ mysql_mutex_destroy(&LOCK_ha_data);
+ my_hash_free(&name_hash);
+
+ plugin_unlock(NULL, db_plugin);
+ db_plugin= NULL;
+
+ /* Release fulltext parsers */
+ info_it= key_info;
+ for (idx= keys; idx; idx--, info_it++)
+ {
+ if (info_it->flags & HA_USES_PARSER)
+ {
+ plugin_unlock(NULL, info_it->parser);
+ info_it->flags= 0;
+ }
+ }
+ /*
+ Make a copy since the share is allocated in its own root,
+ and free_root() updates its argument after freeing the memory.
+ */
+ MEM_ROOT own_root= mem_root;
+ free_root(&own_root, MYF(0));
+}
+
/*
Free table share and memory used by it
SYNOPSIS
free_table_share()
share Table share
-
- NOTES
- share->mutex must be locked when we come here if it's not a temp table
*/
void free_table_share(TABLE_SHARE *share)
{
- MEM_ROOT mem_root;
- uint idx;
- KEY *key_info;
DBUG_ENTER("free_table_share");
DBUG_PRINT("enter", ("table: %s.%s", share->db.str, share->table_name.str));
DBUG_ASSERT(share->ref_count == 0);
- /* The mutex is initialized only for shares that are part of the TDC */
- if (share->tmp_table == NO_TMP_TABLE)
- mysql_mutex_destroy(&share->LOCK_ha_data);
- my_hash_free(&share->name_hash);
-
- plugin_unlock(NULL, share->db_plugin);
- share->db_plugin= NULL;
-
- /* Release fulltext parsers */
- key_info= share->key_info;
- for (idx= share->keys; idx; idx--, key_info++)
+ if (share->m_flush_tickets.is_empty())
{
- if (key_info->flags & HA_USES_PARSER)
- {
- plugin_unlock(NULL, key_info->parser);
- key_info->flags= 0;
- }
+ /*
+ No threads are waiting for this share to be flushed (the
+ share is not old, is for a temporary table, or just nobody
+ happens to be waiting for it). Destroy it.
+ */
+ share->destroy();
+ }
+ else
+ {
+ Wait_for_flush_list::Iterator it(share->m_flush_tickets);
+ Wait_for_flush *ticket;
+ /*
+ We're about to iterate over a list that is used
+ concurrently. Make sure this never happens without a lock.
+ */
+ mysql_mutex_assert_owner(&LOCK_open);
+
+ while ((ticket= it++))
+ (void) ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED);
+ /*
+ If there are threads waiting for this share to be flushed,
+ the last one to receive the notification will destroy the
+ share. At this point the share is removed from the table
+ definition cache, so is OK to proceed here without waiting
+ for this thread to do the work.
+ */
}
- /* We must copy mem_root from share because share is allocated through it */
- memcpy((char*) &mem_root, (char*) &share->mem_root, sizeof(mem_root));
- free_root(&mem_root, MYF(0)); // Free's share
DBUG_VOID_RETURN;
}
@@ -2995,6 +3037,192 @@ Table_check_intact::check(TABLE *table, const TABLE_FIELD_DEF *table_def)
}
+/**
+ Traverse portion of wait-for graph which is reachable through edge
+ represented by this flush ticket in search for deadlocks.
+
+ @retval TRUE A deadlock is found. A victim is remembered
+ by the visitor.
+ @retval FALSE Success, no deadlocks.
+*/
+
+bool Wait_for_flush::accept_visitor(MDL_wait_for_graph_visitor *gvisitor)
+{
+ return m_share->visit_subgraph(this, gvisitor);
+}
+
+
+uint Wait_for_flush::get_deadlock_weight() const
+{
+ return m_deadlock_weight;
+}
+
+
+/**
+ Traverse portion of wait-for graph which is reachable through this
+ table share in search for deadlocks.
+
+ @param waiting_ticket Ticket representing wait for this share.
+ @param dvisitor Deadlock detection visitor.
+
+ @retval TRUE A deadlock is found. A victim is remembered
+ by the visitor.
+ @retval FALSE No deadlocks, it's OK to begin wait.
+*/
+
+bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush,
+ MDL_wait_for_graph_visitor *gvisitor)
+{
+ TABLE *table;
+ MDL_context *src_ctx= wait_for_flush->get_ctx();
+ bool result= TRUE;
+
+ /*
+ To protect used_tables list from being concurrently modified
+ while we are iterating through it we acquire LOCK_open.
+ This does not introduce deadlocks in the deadlock detector
+ because we won't try to acquire LOCK_open while
+ holding a write-lock on MDL_lock::m_rwlock.
+ */
+ if (gvisitor->m_lock_open_count++ == 0)
+ mysql_mutex_lock(&LOCK_open);
+
+ I_P_List_iterator <TABLE, TABLE_share> tables_it(used_tables);
+
+ /*
+ In case of multiple searches running in parallel, avoid going
+ over the same loop twice and shortcut the search.
+ Do it after taking the lock to weed out unnecessary races.
+ */
+ if (src_ctx->m_wait.get_status() != MDL_wait::EMPTY)
+ {
+ result= FALSE;
+ goto end;
+ }
+
+ if (gvisitor->enter_node(src_ctx))
+ goto end;
+
+ while ((table= tables_it++))
+ {
+ if (gvisitor->inspect_edge(&table->in_use->mdl_context))
+ {
+ goto end_leave_node;
+ }
+ }
+
+ tables_it.rewind();
+ while ((table= tables_it++))
+ {
+ if (table->in_use->mdl_context.visit_subgraph(gvisitor))
+ {
+ goto end_leave_node;
+ }
+ }
+
+ result= FALSE;
+
+end_leave_node:
+ gvisitor->leave_node(src_ctx);
+
+end:
+ if (gvisitor->m_lock_open_count-- == 1)
+ mysql_mutex_unlock(&LOCK_open);
+
+ return result;
+}
+
+
+/**
+ Wait until the subject share is removed from the table
+ definition cache and make sure it's destroyed.
+
+ @param mdl_context MDL context for thread which is going to wait.
+ @param abstime Timeout for waiting as absolute time value.
+ @param deadlock_weight Weight of this wait for deadlock detector.
+
+ @pre LOCK_open is write locked, the share is used (has
+ non-zero reference count), is marked for flush and
+ this connection does not reference the share.
+ LOCK_open will be unlocked temporarily during execution.
+
+ @retval FALSE - Success.
+ @retval TRUE - Error (OOM, deadlock, timeout, etc...).
+*/
+
+bool TABLE_SHARE::wait_for_old_version(THD *thd, struct timespec *abstime,
+ uint deadlock_weight)
+{
+ MDL_context *mdl_context= &thd->mdl_context;
+ Wait_for_flush ticket(mdl_context, this, deadlock_weight);
+ MDL_wait::enum_wait_status wait_status;
+
+ mysql_mutex_assert_owner(&LOCK_open);
+ /*
+ We should enter this method only when share's version is not
+ up to date and the share is referenced. Otherwise our
+ thread will never be woken up from wait.
+ */
+ DBUG_ASSERT(version != refresh_version && ref_count != 0);
+
+ m_flush_tickets.push_front(&ticket);
+
+ mdl_context->m_wait.reset_status();
+
+ mysql_mutex_unlock(&LOCK_open);
+
+ mdl_context->will_wait_for(&ticket);
+
+ mdl_context->find_deadlock();
+
+ wait_status= mdl_context->m_wait.timed_wait(thd, abstime, TRUE,
+ "Waiting for table flush");
+
+ mdl_context->done_waiting_for();
+
+ mysql_mutex_lock(&LOCK_open);
+
+ m_flush_tickets.remove(&ticket);
+
+ if (m_flush_tickets.is_empty() && ref_count == 0)
+ {
+ /*
+ If our thread was the last one using the share,
+ we must destroy it here.
+ */
+ destroy();
+ }
+
+ /*
+ In cases when our wait was aborted by KILL statement,
+ a deadlock or a timeout, the share might still be referenced,
+ so we don't delete it. Note, that we can't determine this
+ condition by checking wait_status alone, since, for example,
+ a timeout can happen after all references to the table share
+ were released, but before the share is removed from the
+ cache and we receive the notification. This is why
+ we first destroy the share, and then look at
+ wait_status.
+ */
+ switch (wait_status)
+ {
+ case MDL_wait::GRANTED:
+ return FALSE;
+ case MDL_wait::VICTIM:
+ my_error(ER_LOCK_DEADLOCK, MYF(0));
+ return TRUE;
+ case MDL_wait::TIMEOUT:
+ my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
+ return TRUE;
+ case MDL_wait::KILLED:
+ return TRUE;
+ default:
+ DBUG_ASSERT(0);
+ return TRUE;
+ }
+}
+
+
/*
Create Item_field for each column in the table.
diff --git a/sql/table.h b/sql/table.h
index 6ed3ec0a921..bce1124fbb3 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -507,7 +507,46 @@ public:
};
-/*
+/**
+ Class representing the fact that some thread waits for table
+ share to be flushed. Is used to represent information about
+ such waits in MDL deadlock detector.
+*/
+
+class Wait_for_flush : public MDL_wait_for_subgraph
+{
+ MDL_context *m_ctx;
+ TABLE_SHARE *m_share;
+ uint m_deadlock_weight;
+public:
+ Wait_for_flush(MDL_context *ctx_arg, TABLE_SHARE *share_arg,
+ uint deadlock_weight_arg)
+ : m_ctx(ctx_arg), m_share(share_arg),
+ m_deadlock_weight(deadlock_weight_arg)
+ {}
+
+ MDL_context *get_ctx() const { return m_ctx; }
+
+ virtual bool accept_visitor(MDL_wait_for_graph_visitor *dvisitor);
+
+ virtual uint get_deadlock_weight() const;
+
+ /**
+ Pointers for participating in the list of waiters for table share.
+ */
+ Wait_for_flush *next_in_share;
+ Wait_for_flush **prev_in_share;
+};
+
+
+typedef I_P_List <Wait_for_flush,
+ I_P_List_adapter<Wait_for_flush,
+ &Wait_for_flush::next_in_share,
+ &Wait_for_flush::prev_in_share> >
+ Wait_for_flush_list;
+
+
+/**
This structure is shared between different table objects. There is one
instance of table share per one table in the database.
*/
@@ -661,6 +700,11 @@ struct TABLE_SHARE
/** Instrumentation for this table share. */
PSI_table_share *m_psi;
+ /**
+ List of tickets representing threads waiting for the share to be flushed.
+ */
+ Wait_for_flush_list m_flush_tickets;
+
/*
Set share's table cache key and update its db and table name appropriately.
@@ -730,10 +774,8 @@ struct TABLE_SHARE
}
- /*
- Must all TABLEs be reopened?
- */
- inline bool needs_reopen() const
+ /** Is this table share being expelled from the table definition cache? */
+ inline bool has_old_version() const
{
return version != refresh_version;
}
@@ -836,6 +878,13 @@ struct TABLE_SHARE
return (tmp_table == SYSTEM_TMP_TABLE || is_view) ? 0 : table_map_id;
}
+ bool visit_subgraph(Wait_for_flush *waiting_ticket,
+ MDL_wait_for_graph_visitor *gvisitor);
+
+ bool wait_for_old_version(THD *thd, struct timespec *abstime,
+ uint deadlock_weight);
+ /** Release resources and free memory occupied by the table share. */
+ void destroy();
};
@@ -1083,9 +1132,7 @@ public:
read_set= &def_read_set;
write_set= &def_write_set;
}
- /*
- Is this instance of the table should be reopen?
- */
+ /** Should this instance of the table be reopened? */
inline bool needs_reopen()
{ return !db_stat || m_needs_reopen; }