diff options
Diffstat (limited to 'sql/table_cache.cc')
-rw-r--r-- | sql/table_cache.cc | 134 |
1 files changed, 84 insertions, 50 deletions
diff --git a/sql/table_cache.cc b/sql/table_cache.cc index fbafd8e0c9f..8b768240b4f 100644 --- a/sql/table_cache.cc +++ b/sql/table_cache.cc @@ -70,13 +70,6 @@ static int32 tc_count; /**< Number of TABLE objects in table cache. */ /** - Protects TABLE_SHARE::tdc.free_tables, TABLE_SHARE::tdc.all_tables. -*/ - -mysql_mutex_t LOCK_open; - - -/** Protects unused shares list. TABLE_SHARE::tdc.prev @@ -90,11 +83,9 @@ static mysql_rwlock_t LOCK_tdc; /**< Protects tdc_hash. */ my_atomic_rwlock_t LOCK_tdc_atomics; /**< Protects tdc_version. */ #ifdef HAVE_PSI_INTERFACE -static PSI_mutex_key key_LOCK_open, key_LOCK_unused_shares, - key_TABLE_SHARE_LOCK_table_share; +static PSI_mutex_key key_LOCK_unused_shares, key_TABLE_SHARE_LOCK_table_share; static PSI_mutex_info all_tc_mutexes[]= { - { &key_LOCK_open, "LOCK_open", PSI_FLAG_GLOBAL }, { &key_LOCK_unused_shares, "LOCK_unused_shares", PSI_FLAG_GLOBAL }, { &key_TABLE_SHARE_LOCK_table_share, "TABLE_SHARE::tdc.LOCK_table_share", 0 } }; @@ -171,6 +162,33 @@ static void tc_remove_table(TABLE *table) /** + Wait for MDL deadlock detector to complete traversing tdc.all_tables. + + Must be called before updating TABLE_SHARE::tdc.all_tables. +*/ + +static void tc_wait_for_mdl_deadlock_detector(TABLE_SHARE *share) +{ + while (share->tdc.all_tables_refs) + mysql_cond_wait(&share->tdc.COND_release, &share->tdc.LOCK_table_share); +} + + +/** + Get last element of tdc.free_tables. +*/ + +static TABLE *tc_free_tables_back(TABLE_SHARE *share) +{ + TABLE_SHARE::TABLE_list::Iterator it(share->tdc.free_tables); + TABLE *entry, *last= 0; + while ((entry= it++)) + last= entry; + return last; +} + + +/** Free all unused TABLE objects. While locked: @@ -193,9 +211,11 @@ void tc_purge(bool mark_flushed) TABLE_SHARE::TABLE_list purge_tables; tdc_it.init(); - mysql_mutex_lock(&LOCK_open); while ((share= tdc_it.next())) { + mysql_mutex_lock(&share->tdc.LOCK_table_share); + tc_wait_for_mdl_deadlock_detector(share); + if (mark_flushed) share->tdc.flushed= true; while ((table= share->tdc.free_tables.pop_front())) @@ -203,9 +223,9 @@ void tc_purge(bool mark_flushed) tc_remove_table(table); purge_tables.push_front(table); } + mysql_mutex_unlock(&share->tdc.LOCK_table_share); } tdc_it.deinit(); - mysql_mutex_unlock(&LOCK_open); while ((table= purge_tables.pop_front())) intern_close_table(table); @@ -232,9 +252,10 @@ void tc_add_table(THD *thd, TABLE *table) { bool need_purge; DBUG_ASSERT(table->in_use == thd); - mysql_mutex_lock(&LOCK_open); + mysql_mutex_lock(&table->s->tdc.LOCK_table_share); + tc_wait_for_mdl_deadlock_detector(table->s); table->s->tdc.all_tables.push_front(table); - mysql_mutex_unlock(&LOCK_open); + mysql_mutex_unlock(&table->s->tdc.LOCK_table_share); /* If we have too many TABLE instances around, try to get rid of them */ my_atomic_rwlock_wrlock(&LOCK_tdc_atomics); @@ -243,31 +264,48 @@ void tc_add_table(THD *thd, TABLE *table) if (need_purge) { - TABLE *purge_table= 0; + TABLE_SHARE *purge_share= 0; TABLE_SHARE *share; + TABLE *entry; + ulonglong purge_time; TDC_iterator tdc_it; tdc_it.init(); - mysql_mutex_lock(&LOCK_open); while ((share= tdc_it.next())) { - TABLE_SHARE::TABLE_list::Iterator it(share->tdc.free_tables); - TABLE *entry; - while ((entry= it++)) - if (!purge_table || entry->tc_time < purge_table->tc_time) - purge_table= entry; + mysql_mutex_lock(&share->tdc.LOCK_table_share); + if ((entry= tc_free_tables_back(share)) && + (!purge_share || entry->tc_time < purge_time)) + { + purge_share= share; + purge_time= entry->tc_time; + } + mysql_mutex_unlock(&share->tdc.LOCK_table_share); } - tdc_it.deinit(); - if (purge_table) + if (purge_share) { - purge_table->s->tdc.free_tables.remove(purge_table); - tc_remove_table(purge_table); - mysql_mutex_unlock(&LOCK_open); - intern_close_table(purge_table); + mysql_mutex_lock(&purge_share->tdc.LOCK_table_share); + tc_wait_for_mdl_deadlock_detector(purge_share); + tdc_it.deinit(); + /* + It may happen that oldest table was acquired meanwhile. In this case + just go ahead, number of objects in table cache will normalize + eventually. + */ + if ((entry= tc_free_tables_back(purge_share)) && + entry->tc_time == purge_time) + { + entry->s->tdc.free_tables.remove(entry); + tc_remove_table(entry); + mysql_mutex_unlock(&purge_share->tdc.LOCK_table_share); + intern_close_table(entry); + } + else + mysql_mutex_unlock(&purge_share->tdc.LOCK_table_share); } else - mysql_mutex_unlock(&LOCK_open); + tdc_it.deinit(); } } @@ -292,10 +330,8 @@ static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share) { TABLE *table; - mysql_mutex_lock(&LOCK_open); + mysql_mutex_lock(&share->tdc.LOCK_table_share); table= share->tdc.free_tables.pop_front(); - mysql_mutex_unlock(&LOCK_open); - if (table) { DBUG_ASSERT(!table->in_use); @@ -305,6 +341,7 @@ static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share) /* The children must be detached from the table. */ DBUG_ASSERT(!table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN)); } + mysql_mutex_unlock(&share->tdc.LOCK_table_share); return table; } @@ -328,7 +365,7 @@ static TABLE *tc_acquire_table(THD *thd, TABLE_SHARE *share) @note Another thread may mark share for purge any moment (even after version check). It means to-be-purged object may go to unused lists. This other thread is expected to call tc_purge(), - which is synchronized with us on LOCK_open. + which is synchronized with us on TABLE_SHARE::tdc.LOCK_table_share. @return @retval true object purged @@ -342,17 +379,17 @@ bool tc_release_table(TABLE *table) if (table->needs_reopen() || tc_records() > tc_size) { - mysql_mutex_lock(&LOCK_open); + mysql_mutex_lock(&table->s->tdc.LOCK_table_share); goto purge; } table->tc_time= my_interval_timer(); - mysql_mutex_lock(&LOCK_open); + mysql_mutex_lock(&table->s->tdc.LOCK_table_share); if (table->s->tdc.flushed) goto purge; /* - in_use doesn't really need protection of LOCK_open, but must be reset after + in_use doesn't really need mutex protection, but must be reset after checking tdc.flushed and before this table appears in free_tables. Resetting in_use is needed only for print_cached_tables() and list_open_tables(). @@ -360,12 +397,13 @@ bool tc_release_table(TABLE *table) table->in_use= 0; /* Add table to the list of unused TABLE objects for this share. */ table->s->tdc.free_tables.push_front(table); - mysql_mutex_unlock(&LOCK_open); + mysql_mutex_unlock(&table->s->tdc.LOCK_table_share); return false; purge: + tc_wait_for_mdl_deadlock_detector(table->s); tc_remove_table(table); - mysql_mutex_unlock(&LOCK_open); + mysql_mutex_unlock(&table->s->tdc.LOCK_table_share); table->in_use= 0; intern_close_table(table); return true; @@ -446,13 +484,6 @@ int tdc_init(void) init_tc_psi_keys(); #endif tdc_inited= true; - mysql_mutex_init(key_LOCK_open, &LOCK_open, MY_MUTEX_INIT_FAST); - mysql_mutex_record_order(&LOCK_active_mi, &LOCK_open); - /* - We must have LOCK_open before LOCK_global_system_variables because - LOCK_open is held while sql_plugin.cc::intern_sys_var_ptr() is called. - */ - mysql_mutex_record_order(&LOCK_open, &LOCK_global_system_variables); mysql_mutex_init(key_LOCK_unused_shares, &LOCK_unused_shares, MY_MUTEX_INIT_FAST); mysql_rwlock_init(key_rwlock_LOCK_tdc, &LOCK_tdc); @@ -505,7 +536,6 @@ void tdc_deinit(void) my_atomic_rwlock_destroy(&LOCK_tdc_atomics); mysql_rwlock_destroy(&LOCK_tdc); mysql_mutex_destroy(&LOCK_unused_shares); - mysql_mutex_destroy(&LOCK_open); } DBUG_VOID_RETURN; } @@ -575,6 +605,7 @@ void tdc_init_share(TABLE_SHARE *share) tdc_assign_new_table_id(share); share->tdc.version= tdc_refresh_version(); share->tdc.flushed= false; + share->tdc.all_tables_refs= 0; DBUG_VOID_RETURN; } @@ -590,6 +621,7 @@ void tdc_deinit_share(TABLE_SHARE *share) DBUG_ASSERT(share->tdc.m_flush_tickets.is_empty()); DBUG_ASSERT(share->tdc.all_tables.is_empty()); DBUG_ASSERT(share->tdc.free_tables.is_empty()); + DBUG_ASSERT(share->tdc.all_tables_refs == 0); mysql_cond_destroy(&share->tdc.COND_release); mysql_mutex_destroy(&share->tdc.LOCK_table_share); DBUG_VOID_RETURN; @@ -826,7 +858,8 @@ void tdc_release_share(TABLE_SHARE *share) if (share->tdc.ref_count > 1) { share->tdc.ref_count--; - mysql_cond_broadcast(&share->tdc.COND_release); + if (!share->is_view) + mysql_cond_broadcast(&share->tdc.COND_release); mysql_mutex_unlock(&share->tdc.LOCK_table_share); DBUG_VOID_RETURN; } @@ -954,13 +987,14 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, I_P_List <TABLE, TABLE_share> purge_tables; uint my_refs= 1; - mysql_mutex_lock(&LOCK_open); + mysql_mutex_lock(&share->tdc.LOCK_table_share); + tc_wait_for_mdl_deadlock_detector(share); /* - Set share's version to zero in order to ensure that it gets + Mark share flushed 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 - incrementing of refresh_version is followed by purge of unused table + marking share flushed is followed by purge of unused table shares. */ if (remove_type != TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) @@ -985,7 +1019,7 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, } } DBUG_ASSERT(share->tdc.all_tables.is_empty() || remove_type != TDC_RT_REMOVE_ALL); - mysql_mutex_unlock(&LOCK_open); + mysql_mutex_unlock(&share->tdc.LOCK_table_share); while ((table= purge_tables.pop_front())) intern_close_table(table); |