diff options
Diffstat (limited to 'sql/sql_base.cc')
-rw-r--r-- | sql/sql_base.cc | 216 |
1 files changed, 204 insertions, 12 deletions
diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 630d135be48..6f114165fa6 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -28,6 +28,59 @@ #include <io.h> #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_error(uint sql_errno, + MYSQL_ERROR::enum_warning_level level, + THD *thd); + + bool safely_trapped_errors(); + +private: + int m_handled_errors; + int m_unhandled_errors; +}; + + +bool +Prelock_error_handler::handle_error(uint sql_errno, + MYSQL_ERROR::enum_warning_level /* level */, + THD * /* thd */) +{ + if (sql_errno == ER_NO_SUCH_TABLE) + { + m_handled_errors++; + return TRUE; // 'TRUE', as per coding style + } + + m_unhandled_errors++; + return FALSE; // 'FALSE', as per coding style +} + + +bool Prelock_error_handler::safely_trapped_errors() +{ + /* + If m_unhandled_errors != 0, something else, unanticipated, happened, + so the error is not trapped but returned to the caller. + Multiple ER_NO_SUCH_TABLE can be raised in case of views. + */ + return ((m_handled_errors > 0) && (m_unhandled_errors == 0)); +} + + TABLE *unused_tables; /* Used by mysql_test */ HASH open_cache; /* Used by mysql_test */ @@ -1210,6 +1263,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, int4store(key + key_length, thd->server_id); int4store(key + key_length + 4, thd->variables.pseudo_thread_id); + /* + Unless requested otherwise, try to resolve this table in the list + of temporary tables of this thread. In MySQL temporary tables + are always thread-local and "shadow" possible base tables with the + same name. This block implements the behaviour. + TODO: move this block into a separate function. + */ if (!table_list->skip_temporary) { for (table= thd->temporary_tables; table ; table=table->next) @@ -1218,6 +1278,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, !memcmp(table->s->table_cache_key, key, key_length + TMP_TABLE_KEY_EXTRA)) { + /* + We're trying to use the same temporary table twice in a query. + Right now we don't support this because a temporary table + is always represented by only one TABLE object in THD, and + it can not be cloned. Emit an error for an unsupported behaviour. + */ if (table->query_id == thd->query_id || thd->prelocked_mode && table->query_id) { @@ -1233,6 +1299,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } } + /* + The table is not temporary - if we're in pre-locked or LOCK TABLES + mode, let's try to find the requested table in the list of pre-opened + and locked tables. If the table is not there, return an error - we can't + open not pre-opened tables in pre-locked/LOCK TABLES mode. + TODO: move this block into a separate function. + */ if (!(flags & MYSQL_OPEN_IGNORE_LOCKED_TABLES) && (thd->locked_tables || thd->prelocked_mode)) { // Using table locks @@ -1304,7 +1377,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, goto reset; } /* - is it view? + Is this table a view and not a base table? (it is work around to allow to open view with locked tables, real fix will be made after definition cache will be made) */ @@ -1334,12 +1407,39 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, VOID(pthread_mutex_unlock(&LOCK_open)); } } - my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias); + if ((thd->locked_tables) && (thd->locked_tables->lock_count > 0)) + my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias); + else + my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias); DBUG_RETURN(0); } + /* + Non pre-locked/LOCK TABLES mode, and the table is not temporary: + this is the normal use case. + Now we should: + - try to find the table in the table cache. + - if one of the discovered TABLE instances is name-locked + (table->s->version == 0) or some thread has started FLUSH TABLES + (refresh_version > table->s->version), back off -- we have to wait + until no one holds a name lock on the table. + - if there is no such TABLE in the name cache, read the table definition + and insert it into the cache. + We perform all of the above under LOCK_open which currently protects + the open cache (also known as table cache) and table definitions stored + on disk. + */ + VOID(pthread_mutex_lock(&LOCK_open)); + /* + If it's the first table from a list of tables used in a query, + remember refresh_version (the version of open_cache state). + If the version changes while we're opening the remaining tables, + we will 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 (!thd->open_tables) thd->version=refresh_version; else if ((thd->version != refresh_version) && @@ -1356,12 +1456,39 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, if (thd->handler_tables) mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE, TRUE); + /* + Actually try to find the table in the open_cache. + The cache may contain several "TABLE" instances for the same + physical table. The instances that are currently "in use" by + some thread have their "in_use" member != NULL. + There is no good reason for having more than one entry in the + hash for the same physical table, except that we use this as + an implicit "pending locks queue" - see + wait_for_locked_table_names for details. + */ for (table= (TABLE*) hash_first(&open_cache, (byte*) key, key_length, &state); table && table->in_use ; table= (TABLE*) hash_next(&open_cache, (byte*) key, key_length, &state)) { + /* + Normally, table->s->version contains the value of + refresh_version from the moment when this table was + (re-)opened and added to the cache. + If since then we did (or just started) FLUSH TABLES + statement, refresh_version has been increased. + For "name-locked" TABLE instances, table->s->version is set + to 0 (see lock_table_name for details). + In case there is a pending FLUSH TABLES or a name lock, we + need to back off and re-start opening tables. + If we do not back off now, we may dead lock in case of lock + order mismatch with some other thread: + c1: name lock t1; -- sort of exclusive lock + c2: open t2; -- sort of shared lock + c1: name lock t2; -- blocks + c2: open t1; -- blocks + */ if (table->s->version != refresh_version) { DBUG_PRINT("note", @@ -1375,16 +1502,35 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } /* - There is a refresh in progress for this table - Wait until the table is freed or the thread is killed. + Back off, part 1: mark the table as "unused" for the + purpose of name-locking by setting table->db_stat to 0. Do + that only for the tables in this thread that have an old + table->s->version (this is an optimization (?)). + table->db_stat == 0 signals wait_for_locked_table_names + that the tables in question are not used any more. See + table_is_used call for details. */ close_old_data_files(thd,thd->open_tables,0,0); + /* + Back-off part 2: try to avoid "busy waiting" on the table: + if the table is in use by some other thread, we suspend + and wait till the operation is complete: when any + operation that juggles with table->s->version completes, + it broadcasts COND_refresh condition variable. + */ if (table->in_use != thd) + { wait_for_refresh(thd); + /* wait_for_refresh will unlock LOCK_open for us */ + } else { VOID(pthread_mutex_unlock(&LOCK_open)); } + /* + There is a refresh in progress for this table. + Signal the caller that it has to try again. + */ if (refresh) *refresh=1; DBUG_RETURN(0); @@ -1392,6 +1538,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } if (table) { + /* Unlink the table from "unused_tables" list. */ if (table == unused_tables) { // First unused unused_tables=unused_tables->next; // Remove from link @@ -1404,6 +1551,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } else { + /* Insert a new TABLE instance into the open cache */ TABLE_SHARE *share; int error; /* Free cache if too big */ @@ -2092,6 +2240,8 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) MEM_ROOT new_frm_mem; /* Also used for indicating that prelocking is need */ TABLE_LIST **query_tables_last_own; + bool safe_to_ignore_table; + DBUG_ENTER("open_tables"); /* temporary mem_root for new .frm parsing. @@ -2145,8 +2295,13 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) } } + /* + For every table in the list of tables to open, try to find or open + a table. + */ for (tables= *start; tables ;tables= tables->next_global) { + safe_to_ignore_table= FALSE; // 'FALSE', as per coding style /* Ignore placeholders for derived tables. After derived tables processing, link to created temporary table will be put here. @@ -2159,6 +2314,12 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) goto process_view_routines; continue; } + /* + If this TABLE_LIST object is a placeholder for an information_schema + table, create a temporary table to represent the information_schema + table in the query. Do not fill it yet - will be filled during + execution. + */ if (tables->schema_table) { if (!mysql_schema_table(thd, thd->lex, tables)) @@ -2166,9 +2327,32 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) DBUG_RETURN(-1); } (*counter)++; - - if (!tables->table && - !(tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags))) + + /* + Not a placeholder: must be a base table or a view, and the table is + not opened yet. Try to open the table. + */ + if (!tables->table) + { + if (tables->prelocking_placeholder) + { + /* + For the tables added by the pre-locking code, attempt to open + the table but fail silently if the table does not exist. + 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); + tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags); + thd->pop_internal_handler(); + safe_to_ignore_table= prelock_handler.safely_trapped_errors(); + } + else + tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags); + } + + if (!tables->table) { free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); @@ -2219,6 +2403,14 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) close_tables_for_reopen(thd, start); goto restart; } + + if (safe_to_ignore_table) + { + DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'", + tables->db, tables->alias)); + continue; + } + result= -1; // Fatal error break; } @@ -2273,7 +2465,7 @@ process_view_routines: { /* Serious error during reading stored routines from mysql.proc table. - Something's wrong with the table or its contents, and an error has + Something is wrong with the table or its contents, and an error has been emitted; we must abort. */ result= -1; @@ -2522,7 +2714,7 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags) static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table) { for (; table; table= table->next_global) - if (!table->placeholder() && !table->schema_table) + if (!table->placeholder()) table->table->query_id= 0; } @@ -2594,7 +2786,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) DBUG_RETURN(-1); for (table= tables; table; table= table->next_global) { - if (!table->placeholder() && !table->schema_table) + if (!table->placeholder()) *(ptr++)= table->table; } @@ -2636,7 +2828,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) for (table= tables; table != first_not_own; table= table->next_global) { - if (!table->placeholder() && !table->schema_table) + if (!table->placeholder()) { table->table->query_id= thd->query_id; if (check_lock_and_start_stmt(thd, table->table, table->lock_type)) @@ -2663,7 +2855,7 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) TABLE_LIST *first_not_own= thd->lex->first_not_own_table(); for (table= tables; table != first_not_own; table= table->next_global) { - if (!table->placeholder() && !table->schema_table && + if (!table->placeholder() && check_lock_and_start_stmt(thd, table->table, table->lock_type)) { ha_rollback_stmt(thd); |