diff options
Diffstat (limited to 'sql/sql_handler.cc')
-rw-r--r-- | sql/sql_handler.cc | 437 |
1 files changed, 272 insertions, 165 deletions
diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index ab3f2797405..dfa06495e9d 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -1,4 +1,5 @@ -/* Copyright (C) 2000-2004 MySQL AB +/* Copyright (C) 2000-2004 MySQL AB, 2008-2009 Sun Microsystems, Inc + 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. @@ -33,27 +34,21 @@ */ /* - There are two containers holding information about open handler tables. - The first is 'thd->handler_tables'. It is a linked list of TABLE objects. - It is used like 'thd->open_tables' in the table cache. The trick is to - exchange these two lists during open and lock of tables. Thus the normal - table cache code can be used. - The second container is a HASH. It holds objects of the type TABLE_LIST. - Despite its name, no lists of tables but only single structs are hashed - (the 'next' pointer is always NULL). The reason for theis second container - is, that we want handler tables to survive FLUSH TABLE commands. A table - affected by FLUSH TABLE must be closed so that other threads are not - blocked by handler tables still in use. Since we use the normal table cache - functions with 'thd->handler_tables', the closed tables are removed from - this list. Hence we need the original open information for the handler - table in the case that it is used again. This information is handed over - to mysql_ha_open() as a TABLE_LIST. So we store this information in the - second container, where it is not affected by FLUSH TABLE. The second - container is implemented as a hash for performance reasons. Consequently, - we use it not only for re-opening a handler table, but also for the - HANDLER ... READ commands. For this purpose, we store a pointer to the - TABLE structure (in the first container) in the TBALE_LIST object in the - second container. When the table is flushed, the pointer is cleared. + The information about open HANDLER objects is stored in a HASH. + It holds objects of type TABLE_LIST, which are indexed by table + name/alias, and allows us to quickly find a HANDLER table for any + operation at hand - be it HANDLER READ or HANDLER CLOSE. + + It also allows us to maintain an "open" HANDLER even in cases + when there is no physically open cursor. E.g. a FLUSH TABLE + statement in this or some other connection demands that all open + HANDLERs against the flushed table are closed. In order to + preserve the information about an open HANDLER, we don't perform + a complete HANDLER CLOSE, but only close the TABLE object. The + corresponding TABLE_LIST is kept in the cache with 'table' + pointer set to NULL. The table will be reopened on next access + (this, however, leads to loss of cursor position, unless the + cursor points at the first record). */ #include "mysql_priv.h" @@ -116,41 +111,28 @@ static void mysql_ha_hash_free(TABLE_LIST *tables) @param thd Thread identifier. @param tables A list of tables with the first entry to close. - @param is_locked If LOCK_open is locked. @note Though this function takes a list of tables, only the first list entry will be closed. @note Broadcasts refresh if it closed a table with old version. */ -static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables, - bool is_locked) +static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables) { - TABLE **table_ptr; - /* - Though we could take the table pointer from hash_tables->table, - we must follow the thd->handler_tables chain anyway, as we need the - address of the 'next' pointer referencing this table - for close_thread_table(). - */ - for (table_ptr= &(thd->handler_tables); - *table_ptr && (*table_ptr != tables->table); - table_ptr= &(*table_ptr)->next) - ; - - if (*table_ptr) + if (tables->table && !tables->table->s->tmp_table) { - (*table_ptr)->file->ha_index_or_rnd_end(); - if (! is_locked) - VOID(pthread_mutex_lock(&LOCK_open)); - if (close_thread_table(thd, table_ptr)) + /* Non temporary table. */ + tables->table->file->ha_index_or_rnd_end(); + tables->table->open_by_handler= 0; + mysql_mutex_lock(&LOCK_open); + if (close_thread_table(thd, &tables->table)) { /* Tell threads waiting for refresh that something has happened */ broadcast_refresh(); } - if (! is_locked) - VOID(pthread_mutex_unlock(&LOCK_open)); + mysql_mutex_unlock(&LOCK_open); + thd->mdl_context.release_lock(tables->mdl_request.ticket); } else if (tables->table) { @@ -159,10 +141,13 @@ static void mysql_ha_close_table(THD *thd, TABLE_LIST *tables, table->file->ha_index_or_rnd_end(); table->query_id= thd->query_id; table->open_by_handler= 0; + mark_tmp_table_for_reuse(table); } /* Mark table as closed, ready for re-open if necessary. */ tables->table= NULL; + /* Safety, cleanup the pointer to satisfy MDL assertions. */ + tables->mdl_request.ticket= NULL; } /* @@ -192,13 +177,19 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) TABLE_LIST *hash_tables = NULL; char *db, *name, *alias; uint dblen, namelen, aliaslen, counter; - int error; + bool error; TABLE *backup_open_tables; + MDL_ticket *mdl_savepoint; DBUG_ENTER("mysql_ha_open"); DBUG_PRINT("enter",("'%s'.'%s' as '%s' reopen: %d", tables->db, tables->table_name, tables->alias, (int) reopen)); + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } if (tables->schema_table) { my_error(ER_WRONG_USAGE, MYF(0), "HANDLER OPEN", @@ -216,7 +207,10 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) HANDLER_TABLES_HASH_SIZE, 0, 0, (my_hash_get_key) mysql_ha_hash_get_key, (my_hash_free_key) mysql_ha_hash_free, 0)) - goto err; + { + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } } else if (! reopen) /* Otherwise we have 'tables' already. */ { @@ -224,10 +218,50 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) strlen(tables->alias) + 1)) { DBUG_PRINT("info",("duplicate '%s'", tables->alias)); + DBUG_PRINT("exit",("ERROR")); my_error(ER_NONUNIQ_TABLE, MYF(0), tables->alias); - goto err; + DBUG_RETURN(TRUE); + } + } + + if (! reopen) + { + /* copy the TABLE_LIST struct */ + dblen= strlen(tables->db) + 1; + namelen= strlen(tables->table_name) + 1; + aliaslen= strlen(tables->alias) + 1; + if (!(my_multi_malloc(MYF(MY_WME), + &hash_tables, (uint) sizeof(*hash_tables), + &db, (uint) dblen, + &name, (uint) namelen, + &alias, (uint) aliaslen, + NullS))) + { + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); + } + /* structure copy */ + *hash_tables= *tables; + hash_tables->db= db; + hash_tables->table_name= name; + hash_tables->alias= alias; + memcpy(hash_tables->db, tables->db, dblen); + memcpy(hash_tables->table_name, tables->table_name, namelen); + memcpy(hash_tables->alias, tables->alias, aliaslen); + hash_tables->mdl_request.init(MDL_key::TABLE, db, name, MDL_SHARED); + /* for now HANDLER can be used only for real TABLES */ + hash_tables->required_type= FRMTYPE_TABLE; + + /* add to hash */ + if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) + { + my_free((char*) hash_tables, MYF(0)); + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); } } + else + hash_tables= tables; /* Save and reset the open_tables list so that open_tables() won't @@ -235,111 +269,83 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) from open_tables(), thd->open_tables will contain only the opened table. - The thd->handler_tables list is kept as-is to avoid deadlocks if - open_table(), called by open_tables(), needs to back-off because - of a pending name-lock on the table being opened. - See open_table() back-off comments for more details. */ backup_open_tables= thd->open_tables; thd->open_tables= NULL; + mdl_savepoint= thd->mdl_context.mdl_savepoint(); /* - open_tables() will set 'tables->table' if successful. + open_tables() will set 'hash_tables->table' if successful. It must be NULL for a real open when calling open_tables(). */ - DBUG_ASSERT(! tables->table); + DBUG_ASSERT(! hash_tables->table); - /* for now HANDLER can be used only for real TABLES */ - tables->required_type= FRMTYPE_TABLE; /* We use open_tables() here, rather than, say, open_ltable() or open_table() because we would like to be able to open a temporary table. */ - error= open_tables(thd, &tables, &counter, 0); - if (thd->open_tables) + error= open_tables(thd, &hash_tables, &counter, 0); + + if (! error && + ! (hash_tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER)) { - if (thd->open_tables->next) - { - /* - We opened something that is more than a single table. - This happens with MERGE engine. Don't try to link - this mess into thd->handler_tables list, close it - and report an error. We must do it right away - because mysql_ha_close_table(), called down the road, - can close a single table only. - */ - close_thread_tables(thd); - my_error(ER_ILLEGAL_HA, MYF(0), tables->alias); - error= 1; - } + my_error(ER_ILLEGAL_HA, MYF(0), tables->alias); + error= TRUE; + } + if (!error && + hash_tables->mdl_request.ticket && + thd->mdl_context.has_lock(mdl_savepoint, + hash_tables->mdl_request.ticket)) + { + /* The ticket returned is within a savepoint. Make a copy. */ + error= thd->mdl_context.clone_ticket(&hash_tables->mdl_request); + hash_tables->table->mdl_ticket= hash_tables->mdl_request.ticket; + } + if (error) + { + close_thread_tables(thd); + thd->open_tables= backup_open_tables; + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + if (!reopen) + my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); else { - /* Merge the opened table into handler_tables list. */ - thd->open_tables->next= thd->handler_tables; - thd->handler_tables= thd->open_tables; + hash_tables->table= NULL; + /* Safety, cleanup the pointer to satisfy MDL assertions. */ + hash_tables->mdl_request.ticket= NULL; } + DBUG_PRINT("exit",("ERROR")); + DBUG_RETURN(TRUE); } - - /* Restore the state. */ thd->open_tables= backup_open_tables; - - if (error) - goto err; - - /* There can be only one table in '*tables'. */ - if (! (tables->table->file->ha_table_flags() & HA_CAN_SQL_HANDLER)) - { - my_error(ER_ILLEGAL_HA, MYF(0), tables->alias); - goto err; - } - - if (! reopen) + if (hash_tables->mdl_request.ticket) { - /* copy the TABLE_LIST struct */ - dblen= strlen(tables->db) + 1; - namelen= strlen(tables->table_name) + 1; - aliaslen= strlen(tables->alias) + 1; - if (!(my_multi_malloc(MYF(MY_WME), - &hash_tables, (uint) sizeof(*hash_tables), - &db, (uint) dblen, - &name, (uint) namelen, - &alias, (uint) aliaslen, - NullS))) - goto err; - /* structure copy */ - *hash_tables= *tables; - hash_tables->db= db; - hash_tables->table_name= name; - hash_tables->alias= alias; - memcpy(hash_tables->db, tables->db, dblen); - memcpy(hash_tables->table_name, tables->table_name, namelen); - memcpy(hash_tables->alias, tables->alias, aliaslen); - - /* add to hash */ - if (my_hash_insert(&thd->handler_tables_hash, (uchar*) hash_tables)) - goto err; + thd->mdl_context. + move_ticket_after_trans_sentinel(hash_tables->mdl_request.ticket); + thd->mdl_context.set_needs_thr_lock_abort(TRUE); } /* + Assert that the above check prevents opening of views and merge tables. + For temporary tables, TABLE::next can be set even if only one table + was opened for HANDLER as it is used to link them together + (see thd->temporary_tables). + */ + DBUG_ASSERT(hash_tables->table->next == NULL || + hash_tables->table->s->tmp_table); + /* If it's a temp table, don't reset table->query_id as the table is - being used by this handler. Otherwise, no meaning at all. + being used by this handler. For non-temp tables we use this flag + in asserts. */ - tables->table->open_by_handler= 1; + hash_tables->table->open_by_handler= 1; if (! reopen) my_ok(thd); DBUG_PRINT("exit",("OK")); DBUG_RETURN(FALSE); - -err: - if (hash_tables) - my_free((char*) hash_tables, MYF(0)); - if (tables->table) - mysql_ha_close_table(thd, tables, FALSE); - DBUG_PRINT("exit",("ERROR")); - DBUG_RETURN(TRUE); } @@ -367,11 +373,16 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables) DBUG_PRINT("enter",("'%s'.'%s' as '%s'", tables->db, tables->table_name, tables->alias)); + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } if ((hash_tables= (TABLE_LIST*) my_hash_search(&thd->handler_tables_hash, (uchar*) tables->alias, strlen(tables->alias) + 1))) { - mysql_ha_close_table(thd, hash_tables, FALSE); + mysql_ha_close_table(thd, hash_tables); my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); } else @@ -381,12 +392,69 @@ bool mysql_ha_close(THD *thd, TABLE_LIST *tables) DBUG_RETURN(TRUE); } + /* + Mark MDL_context as no longer breaking protocol if we have + closed last HANDLER. + */ + if (! thd->handler_tables_hash.records) + thd->mdl_context.set_needs_thr_lock_abort(FALSE); + my_ok(thd); DBUG_PRINT("exit", ("OK")); DBUG_RETURN(FALSE); } +/** + A helper class to process an error from mysql_lock_tables(). + HANDLER READ statement's attempt to lock the subject table + may get aborted if there is a pending DDL. In that case + we close the table, reopen it, and try to read again. + This is implicit and obscure, since HANDLER position + is lost in the process, but it's the legacy server + behaviour we should preserve. +*/ + +class Sql_handler_lock_error_handler: public Internal_error_handler +{ +public: + 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 need_reopen() const { return m_need_reopen; }; + void init() { m_need_reopen= FALSE; }; +private: + bool m_need_reopen; +}; + + +/** + Handle an error from mysql_lock_tables(). + Ignore ER_LOCK_ABORTED errors. +*/ + +bool +Sql_handler_lock_error_handler:: +handle_condition(THD *thd, + uint sql_errno, + const char *sqlstate, + MYSQL_ERROR::enum_warning_level level, + const char* msg, + MYSQL_ERROR **cond_hdl) +{ + *cond_hdl= NULL; + if (sql_errno == ER_LOCK_ABORTED) + m_need_reopen= TRUE; + + return m_need_reopen; +} + + /* Read from a HANDLER table. @@ -424,11 +492,17 @@ bool mysql_ha_read(THD *thd, TABLE_LIST *tables, uint num_rows; uchar *UNINIT_VAR(key); uint UNINIT_VAR(key_len); - bool need_reopen; + Sql_handler_lock_error_handler sql_handler_lock_error; DBUG_ENTER("mysql_ha_read"); DBUG_PRINT("enter",("'%s'.'%s' as '%s'", tables->db, tables->table_name, tables->alias)); + if (thd->locked_tables_mode) + { + my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0)); + DBUG_RETURN(TRUE); + } + thd->lex->select_lex.context.resolve_in_table_list_only(tables); list.push_front(new Item_field(&thd->lex->select_lex.context, NULL, NULL, "*")); @@ -460,60 +534,47 @@ retry: hash_tables->db, hash_tables->table_name, hash_tables->alias, table)); } - table->pos_in_table_list= tables; -#if MYSQL_VERSION_ID < 40100 - if (*tables->db && strcmp(table->table_cache_key, tables->db)) - { - DBUG_PRINT("info",("wrong db")); - table= NULL; - } -#endif } else table= NULL; if (!table) { -#if MYSQL_VERSION_ID < 40100 - char buff[MAX_DBKEY_LENGTH]; - if (*tables->db) - strxnmov(buff, sizeof(buff)-1, tables->db, ".", tables->table_name, - NullS); - else - strncpy(buff, tables->alias, sizeof(buff)); - my_error(ER_UNKNOWN_TABLE, MYF(0), buff, "HANDLER"); -#else my_error(ER_UNKNOWN_TABLE, MYF(0), tables->alias, "HANDLER"); -#endif goto err0; } - tables->table=table; /* save open_tables state */ backup_open_tables= thd->open_tables; + /* Always a one-element list, see mysql_ha_open(). */ + DBUG_ASSERT(hash_tables->table->next == NULL || + hash_tables->table->s->tmp_table); /* mysql_lock_tables() needs thd->open_tables to be set correctly to - be able to handle aborts properly. When the abort happens, it's - safe to not protect thd->handler_tables because it won't close any - tables. + be able to handle aborts properly. */ - thd->open_tables= thd->handler_tables; + thd->open_tables= hash_tables->table; + + + sql_handler_lock_error.init(); + thd->push_internal_handler(&sql_handler_lock_error); - lock= mysql_lock_tables(thd, &tables->table, 1, - MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN, &need_reopen); + lock= mysql_lock_tables(thd, &thd->open_tables, 1, 0); - /* restore previous context */ + thd->pop_internal_handler(); + /* + In 5.1 and earlier, mysql_lock_tables() could replace the TABLE + object with another one (reopen it). This is no longer the case + with new MDL. + */ + DBUG_ASSERT(hash_tables->table == thd->open_tables); + /* Restore previous context. */ thd->open_tables= backup_open_tables; - if (need_reopen) + if (sql_handler_lock_error.need_reopen()) { - mysql_ha_close_table(thd, hash_tables, FALSE); - /* - The lock might have been aborted, we need to manually reset - thd->some_tables_deleted because handler's tables are closed - in a non-standard way. Otherwise we might loop indefinitely. - */ - thd->some_tables_deleted= 0; + DBUG_ASSERT(!lock && !thd->is_error()); + mysql_ha_close_table(thd, hash_tables); goto retry; } @@ -521,7 +582,8 @@ retry: goto err0; // mysql_lock_tables() printed error message already // Always read all columns - tables->table->read_set= &tables->table->s->all_set; + hash_tables->table->read_set= &hash_tables->table->s->all_set; + tables->table= hash_tables->table; if (cond) { @@ -734,12 +796,11 @@ static TABLE_LIST *mysql_ha_find(THD *thd, TABLE_LIST *tables) @param thd Thread identifier. @param tables The list of tables to remove. - @param is_locked If LOCK_open is locked. @note Broadcasts refresh if it closed a table with old version. */ -void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked) +void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables) { TABLE_LIST *hash_tables, *next; DBUG_ENTER("mysql_ha_rm_tables"); @@ -752,11 +813,18 @@ void mysql_ha_rm_tables(THD *thd, TABLE_LIST *tables, bool is_locked) { next= hash_tables->next_local; if (hash_tables->table) - mysql_ha_close_table(thd, hash_tables, is_locked); + mysql_ha_close_table(thd, hash_tables); my_hash_delete(&thd->handler_tables_hash, (uchar*) hash_tables); hash_tables= next; } + /* + Mark MDL_context as no longer breaking protocol if we have + closed last HANDLER. + */ + if (! thd->handler_tables_hash.records) + thd->mdl_context.set_needs_thr_lock_abort(FALSE); + DBUG_VOID_RETURN; } @@ -775,13 +843,28 @@ void mysql_ha_flush(THD *thd) TABLE_LIST *hash_tables; DBUG_ENTER("mysql_ha_flush"); - safe_mutex_assert_owner(&LOCK_open); + mysql_mutex_assert_not_owner(&LOCK_open); + + /* + Don't try to flush open HANDLERs when we're working with + system tables. The main MDL context is backed up and we can't + properly release HANDLER locks stored there. + */ + if (thd->state_flags & Open_tables_state::BACKUPS_AVAIL) + DBUG_VOID_RETURN; for (uint i= 0; i < thd->handler_tables_hash.records; i++) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); - if (hash_tables->table && hash_tables->table->needs_reopen_or_name_lock()) - mysql_ha_close_table(thd, hash_tables, TRUE); + /* + TABLE::mdl_ticket is 0 for temporary tables so we need extra check. + */ + if (hash_tables->table && + ((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()))) + mysql_ha_close_table(thd, hash_tables); } DBUG_VOID_RETURN; @@ -805,7 +888,7 @@ void mysql_ha_cleanup(THD *thd) { hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); if (hash_tables->table) - mysql_ha_close_table(thd, hash_tables, FALSE); + mysql_ha_close_table(thd, hash_tables); } my_hash_free(&thd->handler_tables_hash); @@ -813,3 +896,27 @@ void mysql_ha_cleanup(THD *thd) DBUG_VOID_RETURN; } + +/** + Move tickets for metadata locks corresponding to open HANDLERs + after transaction sentinel in order to protect them from being + released at the end of transaction. + + @param thd Thread identifier. +*/ + +void mysql_ha_move_tickets_after_trans_sentinel(THD *thd) +{ + TABLE_LIST *hash_tables; + DBUG_ENTER("mysql_ha_move_tickets_after_trans_sentinel"); + + for (uint i= 0; i < thd->handler_tables_hash.records; i++) + { + hash_tables= (TABLE_LIST*) my_hash_element(&thd->handler_tables_hash, i); + if (hash_tables->table && hash_tables->table->mdl_ticket) + thd->mdl_context. + move_ticket_after_trans_sentinel(hash_tables->table->mdl_ticket); + } + DBUG_VOID_RETURN; +} + |