List:Commits« Previous MessageNext Message »
From:Dmitry Lenev Date:May 11 2012 4:06pm
Subject:bzr push into mysql-trunk branch (Dmitry.Lenev:3891 to 3892) WL#5772
View as plain text  
 3892 Dmitry Lenev	2012-05-11
      WL#5772 "Add partitioned Table Definition Cache to avoid
      using LOCK_open and its derivatives in DML queries".
      
      Review changes #4:
      - Make Table_cache API object-oriented.
      - Get rid of hash look-ups when iterating over
        all used TABLE instances for the TABLE_SHARE.
      - Improve comments.

    modified:
      sql/mysqld.cc
      sql/sql_base.cc
      sql/sql_base.h
      sql/sql_const.h
      sql/sql_handler.cc
      sql/sql_parse.cc
      sql/sql_table.cc
      sql/sql_test.cc
      sql/sys_vars.cc
      sql/table.cc
      sql/table.h
 3891 Dmitry Lenev	2012-05-04
      WL#5772 "Add partitioned Table Definition Cache to avoid
      using LOCK_open and its derivatives in DML queries".
      
      Review change #3: Use I_P_List_adapter<> instead of
      hand-written adapter class.

    modified:
      sql/sql_base.h
      sql/table.h
=== modified file 'sql/mysqld.cc'
--- a/sql/mysqld.cc	2012-04-20 12:37:01 +0000
+++ b/sql/mysqld.cc	2012-05-11 16:05:27 +0000
@@ -44,7 +44,7 @@
 #include "sql_acl.h"      // acl_free, grant_free, acl_init,
                           // grant_init
 #include "sql_base.h"     // table_def_free, table_def_init,
-                          // cached_open_tables,
+                          // Table_cache,
                           // cached_table_definitions
 #include "sql_test.h"     // mysql_print_status
 #include "item_create.h"  // item_create_cleanup, item_create_init
@@ -6918,7 +6918,7 @@ static int show_open_tables(THD *thd, SH
 {
   var->type= SHOW_LONG;
   var->value= buff;
-  *((long *)buff)= (long)cached_open_tables();
+  *((long *)buff)= (long)Table_cache::cached_tables();
   return 0;
 }
 

=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc	2012-04-27 10:00:16 +0000
+++ b/sql/sql_base.cc	2012-05-11 16:05:27 +0000
@@ -158,34 +158,36 @@ Repair_mrg_table_error_handler::handle_c
 */
 
 /**
-  LOCK_open protects the following variables:
+  LOCK_open protects the following variables/objects:
+
   1) The table_def_cache
-    This is the hash table mapping table name to a table
-    share object. The hash table can only be manipulated
-    while holding LOCK_open.
+     This is the hash table mapping table name to a table
+     share object. The hash table can only be manipulated
+     while holding LOCK_open.
   2) last_table_id
-    Generation of a new unique table_map_id for a table
-    share is done through incrementing last_table_id, a
-    global variable used for this purpose.
+     Generation of a new unique table_map_id for a table
+     share is done through incrementing last_table_id, a
+     global variable used for this purpose.
   3) LOCK_open protects the initialisation of the table share
-    object and all its members and also protects reading the
-    .frm file from where the table share is initialised.
+     object and all its members and also protects reading the
+     .frm file from where the table share is initialised.
   4) In particular the share->ref_count is updated each time
-    a new table object is created that refers to a table share.
-    This update is protected by LOCK_open.
+     a new table object is created that refers to a table share.
+     This update is protected by LOCK_open.
   5) oldest_unused_share, end_of_unused_share and share->next
-    and share->prev are variables to handle the lists of table
-    share objects, these can only be read and manipulated while
-    holding the LOCK_open mutex.
-  6) table_def_shutdown_in_progress
+     and share->prev are variables to handle the lists of table
+     share objects, these can only be read and manipulated while
+     holding the LOCK_open mutex.
+  6) table_def_shutdown_in_progress can be updated only while
+     holding LOCK_open and ALL table cache mutexes.
   7) refresh_version
-    This variable can only be updated while holding LOCK_open AND
-    all table cache mutexes.
+     This variable can only be updated while holding LOCK_open AND
+     all table cache mutexes.
   8) share->version
-    This variable is initialised while holding LOCK_open. It can only
-    be updated while holding LOCK_open AND all table cache mutexes. So
-    if a table share is found through a reference its version won't
-    change if any of those mutexes are held.
+     This variable is initialised while holding LOCK_open. It can only
+     be updated while holding LOCK_open AND all table cache mutexes.
+     So if a table share is found through a reference its version won't
+     change if any of those mutexes are held.
   9) share->m_flush_tickets
 */
 mysql_mutex_t LOCK_open;
@@ -227,14 +229,18 @@ static void modify_slave_open_temp_table
   }
 }
 
-Table_cache table_cache[64];
 
-Table_cache*
-get_table_cache(uint thread_id)
+Table_cache Table_cache::m_table_cache[MAX_TABLE_CACHES];
+
+
+/** Get instance of table cache to be used by particular connection. */
+
+Table_cache* Table_cache::get_cache(THD *thd)
 {
-  return &table_cache[thread_id % table_cache_instances];
+  return &m_table_cache[thd->thread_id % table_cache_instances];
 }
 
+
 extern "C" uchar *table_cache_key(const uchar *record,
                                   size_t *length,
                                   my_bool not_used __attribute__((unused)))
@@ -244,83 +250,227 @@ extern "C" uchar *table_cache_key(const
   return (uchar*) entry->share->table_cache_key.str;
 }
 
-static void
-table_cache_free_entry(Table_cache_element *element)
+
+static void table_cache_free_entry(Table_cache_element *element)
 {
   delete element;
 }
 
+
+/**
+  Initialize instance of table cache.
+
+  @retval false - success.
+  @retval true  - failure.
+*/
+
 bool Table_cache::init()
 {
-  int ret_code;
-  mysql_mutex_init(key_LOCK_table_cache, &lock, MY_MUTEX_INIT_FAST);
-  unused_tables= NULL;
-  table_count= 0;
-
-  ret_code= my_hash_init(&cache,
-                         &my_charset_bin,
-                         table_cache_size_per_instance,
-                         0, 0, table_cache_key,
-                         (my_hash_free_key) table_cache_free_entry,
-                         0);
-  if (ret_code != 0)
+  mysql_mutex_init(key_LOCK_table_cache, &m_lock, MY_MUTEX_INIT_FAST);
+  m_unused_tables= NULL;
+  m_table_count= 0;
+
+  if (my_hash_init(&m_cache, &my_charset_bin,
+                   table_cache_size_per_instance, 0, 0,
+                   table_cache_key, (my_hash_free_key) table_cache_free_entry,
+                   0))
+  {
+    mysql_mutex_destroy(&m_lock);
     return true;
-  else
-    return false;
+  }
+  return false;
 }
 
+
+/** Destroy instance of table cache. */
+
 void Table_cache::destroy()
 {
-  my_hash_free(&cache);
-  mysql_mutex_destroy(&lock);
+  my_hash_free(&m_cache);
+  mysql_mutex_destroy(&m_lock);
 }
 
-uint cached_open_tables(void)
+
+/**
+  Initialize all instances of table cache to be used by server.
+
+  @retval false - success.
+  @retval true  - failure.
+*/
+
+bool Table_cache::init_all()
 {
-  uint result= 0;
-  uint i;
-  
-  for (i= 0; i < table_cache_instances; i++)
+  for (uint i= 0; i < table_cache_instances; i++)
   {
-    result+= table_cache[i].table_count;
+    if (m_table_cache[i].init())
+    {
+      for (uint j= 0; j < i; j++)
+        m_table_cache[i].destroy();
+      return true;
+    }
   }
+
+  return false;
+}
+
+
+/** Destroy all instances of table cache which were used by server. */
+
+void Table_cache::destroy_all()
+{
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].destroy();
+}
+
+
+/**
+  Get total number of used and unused TABLE objects in all table caches.
+
+  @note Doesn't require acquisition of table cache locks if inexact number
+        of tables is acceptable.
+*/
+
+uint Table_cache::cached_tables()
+{
+  uint result= 0;
+
+  for (uint i= 0; i < table_cache_instances; i++)
+    result+= m_table_cache[i].m_table_count;
+
   return result;
 }
 
-void
-lock_all_table_caches(void)
+
+/**
+  Acquire locks on all instances of table cache and table definition
+  cache (i.e. LOCK_open).
+
+  TODO: Come up with a better name!
+*/
+
+void Table_cache::lock_all()
 {
-  uint i;
-  for (i= 0; i < table_cache_instances; i++)
-  {
-    mysql_mutex_lock(&table_cache[i].lock);
-  }
+  for (uint i= 0; i < table_cache_instances; i++)
+    mysql_mutex_lock(&m_table_cache[i].m_lock);
+
+  mysql_mutex_lock(&LOCK_open);
 }
 
-void
-unlock_all_table_caches(void)
+
+/**
+  Release locks on all instances of table cache and table definition
+  cache.
+*/
+
+void Table_cache::unlock_all()
+{
+  mysql_mutex_unlock(&LOCK_open);
+
+  for (uint i= 0; i < table_cache_instances; i++)
+    mysql_mutex_unlock(&m_table_cache[i].m_lock);
+}
+
+
+/**
+  Assert that caller owns locks on all instances of table cache
+  and table definition cache.
+*/
+
+void Table_cache::assert_owner_all()
 {
-  uint i;
-  for (i= 0; i < table_cache_instances; i++)
+  for (uint i= 0; i < table_cache_instances; i++)
+    mysql_mutex_assert_owner(&m_table_cache[i].m_lock);
+
+  mysql_mutex_assert_owner(&LOCK_open);
+}
+
+
+/** Acquire lock on table cache instance. */
+
+void Table_cache::lock()
+{
+  mysql_mutex_lock(&m_lock);
+}
+
+
+/** Release lock on table cache instance. */
+
+void Table_cache::unlock()
+{
+  mysql_mutex_unlock(&m_lock);
+}
+
+
+/**
+  Construct iterator over all used TABLE objects for the table share.
+
+  @note Assumes that caller owns locks on all table caches.
+*/
+
+Table_cache_iterator::Table_cache_iterator(const TABLE_SHARE *share_arg)
+  : share(share_arg), current_cache_index(0), current_table(NULL)
+{
+  Table_cache::assert_owner_all();
+
+  move_to_next_table();
+}
+
+
+/** Helper that moves iterator to the next used TABLE for the table share. */
+
+void Table_cache_iterator::move_to_next_table()
+{
+  for (; current_cache_index < table_cache_instances; ++current_cache_index)
   {
-    mysql_mutex_unlock(&table_cache[i].lock);
+    Table_cache_element *el;
+
+    if ((el= share->cache_element[current_cache_index]))
+    {
+      if ((current_table= el->used_tables.front()))
+        break;
+    }
   }
 }
 
-void
-lock_table_cache(uint thread_id)
+
+/**
+  Get current used TABLE instance and move iterator to the next one.
+
+  @note Assumes that caller owns locks on all table caches.
+*/
+
+TABLE* Table_cache_iterator::operator ++(int)
 {
-  uint table_cache_instance= thread_id % table_cache_instances;
-  mysql_mutex_lock(&table_cache[table_cache_instance].lock);
+  Table_cache::assert_owner_all();
+
+  TABLE *result= current_table;
+
+  if (current_table)
+  {
+    Table_cache_element::TABLE_list::Iterator
+      it(share->cache_element[current_cache_index]->used_tables, current_table);
+
+    current_table= ++it;
+
+    if (!current_table)
+    {
+      ++current_cache_index;
+      move_to_next_table();
+    }
+  }
+
+  return result;
 }
 
-void
-unlock_table_cache(uint thread_id)
+
+void Table_cache_iterator::rewind()
 {
-  uint table_cache_instance= thread_id % table_cache_instances;
-  mysql_mutex_unlock(&table_cache[table_cache_instance].lock);
+  current_cache_index= 0;
+  current_table= NULL;
+  move_to_next_table();
 }
 
+
 HASH table_def_cache;
 static TABLE_SHARE *oldest_unused_share, end_of_unused_share;
 static bool table_def_inited= 0;
@@ -330,7 +480,6 @@ static bool check_and_update_table_versi
                                            TABLE_SHARE *table_share);
 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_cache *tc, TABLE *entry);
 static bool
 has_write_table_with_auto_increment(TABLE_LIST *tables);
 static bool
@@ -338,39 +487,68 @@ has_write_table_with_auto_increment_and_
 static bool has_write_table_auto_increment_not_first_in_pk(TABLE_LIST *tables);
 
 
-#ifdef EXTRA_DEBUG
-static void check_unused(Table_cache *tc)
+/**
+  Add table to the tail of unused tables list for table cache
+  (i.e. as the most recently used table in this list).
+*/
+
+void Table_cache::link_unused_table(TABLE *table)
 {
-  tc->check_unused_links(void);
+  if (m_unused_tables)
+  {
+    table->next= m_unused_tables;
+    table->prev= m_unused_tables->prev;
+    m_unused_tables->prev= table;
+    table->prev->next= table;
+  }
+  else
+    m_unused_tables= table->next= table->prev= table;
+  check_unused();
 }
 
-void
-Table_cache::check_unused_links(void)
+
+/** Remove table from the unused tables list for table cache. */
+
+void Table_cache::unlink_unused_table(TABLE *table)
+{
+  table->next->prev= table->prev;
+  table->prev->next= table->next;
+  if (table == m_unused_tables)
+  {
+    m_unused_tables= m_unused_tables->next;
+    if (table == m_unused_tables)
+      m_unused_tables= 0;
+  }
+  check_unused();
+}
+
+
+#ifdef EXTRA_DEBUG
+void Table_cache::check_unused()
 {
   uint count= 0, open_files= 0, idx= 0;
   TABLE *cur_link, *start_link, *entry;
   Table_cache_element *el;
 
-  if ((start_link=cur_link=unused_tables))
+  if ((start_link= cur_link= m_unused_tables))
   {
     do
     {
       if (cur_link != cur_link->next->prev || cur_link != cur_link->prev->next)
       {
-        DBUG_PRINT("error", ("Unused_links aren't linked properly"));
-        return; /* purecov: inspected */
+	DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */
+	return; /* purecov: inspected */
       }
-    } while (count++ < table_count &&
+    } while (count++ < m_table_count &&
 	     (cur_link=cur_link->next) != start_link);
     if (cur_link != start_link)
     {
-      DBUG_PRINT("error",("Unused_links aren't connected"));
-      /* purecov: inspected */
+      DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */
     }
   }
-  for (idx=0 ; idx < cache.records ; idx++)
+  for (idx=0 ; idx < m_cache.records ; idx++)
   {
-    el= (Table_cache_element*) my_hash_element(&cache, idx);
+    el= (Table_cache_element*) my_hash_element(&m_cache, idx);
 
     Table_cache_element::TABLE_list::Iterator it(el->free_tables);
     while ((entry= it++))
@@ -382,8 +560,7 @@ Table_cache::check_unused_links(void)
 
       if (entry->in_use)
       {
-        DBUG_PRINT("error",("Used table is in share's list of unused tables"));
-        /* purecov: inspected */
+        DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */
       }
       count--;
       open_files++;
@@ -393,21 +570,17 @@ Table_cache::check_unused_links(void)
     {
       if (!entry->in_use)
       {
-        DBUG_PRINT("error",("Unused table is in share's list of used tables"));
-        /* purecov: inspected */
+        DBUG_PRINT("error",("Unused table is in share's list of used tables")); /* purecov: inspected */
       }
       open_files++;
     }
   }
   if (count != 0)
   {
-    DBUG_PRINT("error",
-      ("Unused_links doesn't match open_cache: diff: %d",
-      count)); /* purecov: inspected */
+    DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */
+			count)); /* purecov: inspected */
   }
 }
-#else
-#define check_unused(a)
 #endif
 
 
@@ -517,8 +690,6 @@ static void table_def_free_entry(TABLE_S
 
 bool table_def_init(void)
 {
-  uint i, j;
-  table_def_inited= 1;
 #ifdef HAVE_PSI_INTERFACE
   init_tdc_psi_keys();
 #endif
@@ -526,18 +697,18 @@ bool table_def_init(void)
   oldest_unused_share= &end_of_unused_share;
   end_of_unused_share.prev= &oldest_unused_share;
 
-  for (i= 0; i < table_cache_instances; i++)
+  if (Table_cache::init_all())
   {
-    if (table_cache[i].init())
-    {
-      for (j= 0; j < i; j++)
-      {
-        table_cache[i].destroy();
-      }
-      return TRUE;
-    }
+    mysql_mutex_destroy(&LOCK_open);
+    return true;
   }
 
+  /*
+    It is safe to destroy zero-initialized HASH even if its
+    initialization has failed.
+  */
+  table_def_inited= 1;
+
   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;
@@ -554,17 +725,15 @@ void table_def_start_shutdown(void)
 {
   if (table_def_inited)
   {
-    mysql_mutex_lock(&LOCK_open);
+    Table_cache::lock_all();
     /*
       Ensure that TABLE and TABLE_SHARE objects which are created for
       tables that are open during process of plugins' shutdown are
       immediately released. This keeps number of references to engine
       plugins minimal and allows shutdown to proceed smoothly.
-
-      RONM TODO: Dmitry Lenev said FIXME here for some reason??
     */
     table_def_shutdown_in_progress= TRUE;
-    mysql_mutex_unlock(&LOCK_open);
+    Table_cache::unlock_all();
     /* Free all cached but unused TABLEs and TABLE_SHAREs. */
     close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT);
   }
@@ -573,18 +742,14 @@ void table_def_start_shutdown(void)
 
 void table_def_free(void)
 {
-  uint i;
   DBUG_ENTER("table_def_free");
   if (table_def_inited)
   {
     table_def_inited= 0;
     /* Free table definitions. */
     my_hash_free(&table_def_cache);
+    Table_cache::destroy_all();
     mysql_mutex_destroy(&LOCK_open);
-    for (i= 0; i < table_cache_instances; i++)
-    {
-      table_cache[i].destroy();
-    }
   }
   DBUG_VOID_RETURN;
 }
@@ -596,132 +761,242 @@ uint cached_table_definitions(void)
 }
 
 
-/*
-  Auxiliary routines for manipulating with per-share used/unused and
-  global unused lists of TABLE objects and table_cache_count counter.
-  Responsible for preserving invariants between those lists, counter
-  and TABLE::in_use member.
-  In fact those routines implement sort of implicit table cache as
-  part of table definition cache.
+/**
+  Free unused TABLE instances if total number of TABLE objects
+  in table cache has exceeded table_cache_size_per_instance
+  limit.
+
+  @note That we might need to free more than one instance during
+        this call if table_cache_size was changed dynamically.
 */
 
+void Table_cache::free_unused_tables_if_necessary(THD *thd)
+{
+  /* We have too many TABLE instances around let us try to get rid of them. */
+  if (m_table_count > table_cache_size_per_instance && m_unused_tables)
+  {
+    mysql_mutex_lock(&LOCK_open);
+    while (m_table_count > table_cache_size_per_instance &&
+           m_unused_tables)
+    {
+      TABLE *table_to_free= m_unused_tables;
+      remove_table(table_to_free);
+      intern_close_table(table_to_free);
+      thd->status_var.table_open_cache_overflows++;
+    }
+    mysql_mutex_unlock(&LOCK_open);
+  }
+}
+
 
 /**
-   Add newly created TABLE object for table share which is going
-   to be used right away.
+   Add newly created TABLE object which is going to be used right away
+   to the table cache.
+
+   @note Caller should own lock on the table cache.
+
+   @note Sets TABLE::in_use member as side effect.
+
+   @retval false - success.
+   @retval true  - failure.
 */
 
-static void table_el_add_used_table(THD *thd,
-                                    Table_cache *tc,
-                                    Table_cache_element *el,
-                                    TABLE *table)
+bool Table_cache::add_used_table(THD *thd, TABLE *table)
 {
+  Table_cache_element *el;
+
+  mysql_mutex_assert_owner(&m_lock);
+
   DBUG_ASSERT(table->in_use == thd);
+
+  /*
+    Try to get Table_cache_element representing this table in the cache
+    from array in the TABLE_SHARE.
+  */
+  el= table->s->cache_element[cache_index()];
+
+  if (!el)
+  {
+    /*
+      If TABLE_SHARE doesn't have pointer to the element representing table
+      in this cache, the element for the table must be absent from table the
+      cache.
+
+      Allocate new Table_cache_element object and add it to the cache
+      and array in TABLE_SHARE.
+    */
+    DBUG_ASSERT(! my_hash_search(&m_cache,
+                                 (uchar*)table->s->table_cache_key.str,
+                                 table->s->table_cache_key.length));
+
+    if (!(el= new Table_cache_element(table->s)))
+      return true;
+
+    if (my_hash_insert(&m_cache, (uchar*)el))
+    {
+      delete el;
+      return true;
+    }
+
+    table->s->cache_element[cache_index()]= el;
+  }
+
+  /* Add table to the used tables list */
   el->used_tables.push_front(table);
-  tc->table_count++;
+
+  m_table_count++;
+
   table->cache_element= el;
+
+  free_unused_tables_if_necessary(thd);
+
+  return false;
 }
 
 
 /**
    Prepare used or unused TABLE instance for destruction by removing
-   it from share's and global list.
+   it from the table cache.
+
+   @note Caller should own lock on the table cache.
 */
 
-static void table_el_remove_table(Table_cache *tc, TABLE *table)
+void Table_cache::remove_table(TABLE *table)
 {
+  mysql_mutex_assert_owner(&m_lock);
+
   if (table->in_use)
   {
-    /* Remove from per-share chain of used TABLE objects. */
+    /* Remove from per-table chain of used TABLE objects. */
     table->cache_element->used_tables.remove(table);
   }
   else
   {
-    /* Remove from per-share chain of unused TABLE objects. */
+    /* Remove from per-table chain of unused TABLE objects. */
     table->cache_element->free_tables.remove(table);
 
-    /* And global unused chain. */
-    table->next->prev=table->prev;
-    table->prev->next=table->next;
-    if (table == tc->unused_tables)
-    {
-      tc->unused_tables=tc->unused_tables->next;
-      if (table == tc->unused_tables)
-	tc->unused_tables=0;
-    }
-    check_unused(tc);
+    /* And per-cache unused chain. */
+    unlink_unused_table(table);
   }
-  tc->table_count--;
+
+  m_table_count--;
+
   if (table->cache_element->used_tables.is_empty() &&
       table->cache_element->free_tables.is_empty())
-    (void) my_hash_delete(&tc->cache, (uchar*) table->cache_element);
+  {
+    (void) my_hash_delete(&m_cache, (uchar*) table->cache_element);
+    /*
+      Remove reference to deleted cache element from array
+      in the TABLE_SHARE.
+    */
+    table->s->cache_element[cache_index()]= NULL;
+  }
 }
 
 
 /**
-   Mark already existing TABLE instance as used.
+  Get an unused TABLE instance from the table cache.
+
+  @param      thd         Thread context.
+  @param      hash_value  Hash value for the key identifying table.
+  @param      key         Key identifying table.
+  @param      key_length  Length of key for the table.
+  @param[out] share       NULL - if table cache doesn't contain any
+                          information about the table (i.e. doesn't have
+                          neither used nor unused TABLE objects for it).
+                          Pointer to TABLE_SHARE for the table otherwise.
+
+  @note Caller should own lock on the table cache.
+  @note Sets TABLE::in_use member as side effect.
+
+  @retval non-NULL - pointer to unused TABLE object, "share" out-parameter
+                     contains pointer to TABLE_SHARE for this table.
+  @retval NULL     - no unused TABLE object was found, "share" parameter
+                     contains pointer to TABLE_SHARE for this table if there
+                     are used TABLE objects in cache and NULL otherwise.
 */
 
-static void table_el_use_table(THD *thd,
-                               Table_cache *tc,
-                               Table_cache_element *el,
-                               TABLE *table)
+TABLE* Table_cache::get_table(THD *thd, my_hash_value_type hash_value,
+                              const char *key, uint key_length,
+                              TABLE_SHARE **share)
 {
-  DBUG_ASSERT(!table->in_use);
+  Table_cache_element *el;
+  TABLE *table;
+
+  mysql_mutex_assert_owner(&m_lock);
+
+  *share= NULL;
+
+  if (!(el= (Table_cache_element*) my_hash_search_using_hash_value(&m_cache,
+                                     hash_value, (uchar*) key, key_length)))
+    return NULL;
+
+  *share= el->share;
+
+  if ((table= el->free_tables.front()))
+  {
+    DBUG_ASSERT(!table->in_use);
+
+    /*
+      Unlink table from list of unused TABLE objects for this
+      table in this cache.
+    */
+    el->free_tables.remove(table);
 
-  /* Unlink table from list of unused tables for this cache. */
-  el->free_tables.remove(table);
-  /* Unlink able from unused tables list. */
-  if (table == tc->unused_tables)
-  {						// First unused
-    tc->unused_tables=tc->unused_tables->next;	        // Remove from link
-    if (table == tc->unused_tables)
-      tc->unused_tables=0;
+    /* Unlink table from unused tables list for this cache. */
+    unlink_unused_table(table);
+
+    /*
+      Add table to list of used TABLE objects for this table
+      in the table cache.
+    */
+    el->used_tables.push_front(table);
+
+    table->in_use= thd;
+    /* The ex-unused table must be fully functional. */
+    DBUG_ASSERT(table->db_stat && table->file);
+    /* The children must be detached from the table. */
+    DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
   }
-  table->prev->next=table->next;		/* Remove from unused list */
-  table->next->prev=table->prev;
-  check_unused(tc);
-  /* Add table to list of used tables for this cache. */
-  el->used_tables.push_front(table);
-  table->in_use= thd;
-  /* The ex-unused table must be fully functional. */
-  DBUG_ASSERT(table->db_stat && table->file);
-  /* The children must be detached from the table. */
-  DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
+
+  return table;
 }
 
 
 /**
-   Mark already existing used TABLE instance as unused.
+  Put used TABLE instance back to the table cache and mark
+  it as unused.
+
+  @note Caller should own lock on the table cache.
+  @note Sets TABLE::in_use member as side effect.
 */
 
-static void table_el_unuse_table(Table_cache *tc,
-                                 Table_cache_element *el,
-                                 TABLE *table)
+void Table_cache::release_table(THD *thd, TABLE *table)
 {
+  Table_cache_element *el= table->cache_element;
+
+  mysql_mutex_assert_owner(&m_lock);
+
   DBUG_ASSERT(table->in_use);
   DBUG_ASSERT(table->file);
 
   /* We shouldn't put the table to 'unused' list if the share is old. */
   DBUG_ASSERT(! table->s->has_old_version());
 
-  table->in_use= 0;
+  table->in_use= NULL;
 
-  /* Remove table from the list of tables used in this cache. */
+  /* Remove TABLE from the list of used objects for the table in this cache. */
   el->used_tables.remove(table);
-  /* Add table to the list of unused TABLE objects for this cache. */
+  /* Add TABLE to the list of unused objects for the table in this cache. */
   el->free_tables.push_front(table);
-  /* Also link it last in the global list of unused TABLE objects. */
-  if (tc->unused_tables)
-  {
-    table->next=tc->unused_tables;
-    table->prev=tc->unused_tables->prev;
-    tc->unused_tables->prev=table;
-    table->prev->next=table;
-  }
-  else
-    tc->unused_tables=table->next=table->prev=table;
-  check_unused(tc);
+  /* Also link it last in the list of unused TABLE objects for the cache. */
+  link_unused_table(table);
+
+  /*
+    We free the least used tables, not the subject table, to keep the LRU order.
+    Note that in most common case the below call won't free anything.
+  */
+  free_unused_tables_if_necessary(thd);
 }
 
 
@@ -1049,59 +1324,50 @@ OPEN_TABLE_LIST *list_open_tables(THD *t
   int result = 0;
   OPEN_TABLE_LIST **start_list, *open_list;
   TABLE_LIST table_list;
-  uint i;
   DBUG_ENTER("list_open_tables");
 
   memset(&table_list, 0, sizeof(table_list));
   start_list= &open_list;
   open_list=0;
 
-  for (i= 0; i < table_cache_instances; i++)
-  {
-    mysql_mutex_lock(&table_cache[i].lock);
-    mysql_mutex_lock(&LOCK_open);
+  Table_cache::lock_all();
 
-    for (uint idx=0 ; result == 0 && idx < table_cache[i].cache.records; idx++)
-    {
-      Table_cache_element *el= (Table_cache_element*) my_hash_element(
-                                                     &table_cache[i].cache,
-                                                     idx);
-      TABLE_SHARE *share= el->share;
+  for (uint idx=0 ; result == 0 && idx < table_def_cache.records; idx++)
+  {
+    TABLE_SHARE *share= (TABLE_SHARE *)my_hash_element(&table_def_cache, idx);
 
-      if (db && my_strcasecmp(system_charset_info, db, share->db.str))
-        continue;
-      if (wild && wild_compare(share->table_name.str, wild, 0))
-        continue;
+    if (db && my_strcasecmp(system_charset_info, db, share->db.str))
+      continue;
+    if (wild && wild_compare(share->table_name.str, wild, 0))
+      continue;
 
-      /* Check if user has SELECT privilege for any column in the table */
-      table_list.db=         share->db.str;
-      table_list.table_name= share->table_name.str;
-      table_list.grant.privilege=0;
+    /* Check if user has SELECT privilege for any column in the table */
+    table_list.db=         share->db.str;
+    table_list.table_name= share->table_name.str;
+    table_list.grant.privilege=0;
 
-      if (check_table_access(thd,SELECT_ACL,&table_list, TRUE, 1, TRUE))
-        continue;
+    if (check_table_access(thd,SELECT_ACL,&table_list, TRUE, 1, TRUE))
+      continue;
 
-      if (!(*start_list = (OPEN_TABLE_LIST *)
-          sql_alloc(sizeof(**start_list)+share->table_cache_key.length)))
-      {
-        open_list=0;				// Out of memory
-        break;
-      }
-      strmov((*start_list)->table=
-         strmov(((*start_list)->db= (char*) ((*start_list)+1)),
-                                             share->db.str)+1,
-                                             share->table_name.str);
-      (*start_list)->in_use= 0;
-      Table_cache_element::TABLE_list::Iterator it(el->used_tables);
-      while (it++)
-        ++(*start_list)->in_use;
-      (*start_list)->locked= 0;                   /* Obsolete. */
-      start_list= &(*start_list)->next;
-      *start_list=0;
+    if (!(*start_list = (OPEN_TABLE_LIST *)
+	  sql_alloc(sizeof(**start_list)+share->table_cache_key.length)))
+    {
+      open_list=0;				// Out of memory
+      break;
     }
-    mysql_mutex_unlock(&LOCK_open);
-    mysql_mutex_unlock(&table_cache[i].lock);
+    strmov((*start_list)->table=
+	   strmov(((*start_list)->db= (char*) ((*start_list)+1)),
+		  share->db.str)+1,
+	   share->table_name.str);
+    (*start_list)->in_use= 0;
+    Table_cache_iterator it(share);
+    while (it++)
+      ++(*start_list)->in_use;
+    (*start_list)->locked= 0;                   /* Obsolete. */
+    start_list= &(*start_list)->next;
+    *start_list=0;
   }
+  Table_cache::unlock_all();
   DBUG_RETURN(open_list);
 }
 
@@ -1122,33 +1388,11 @@ void intern_close_table(TABLE *table)
   delete table->triggers;
   if (table->file)                              // Not true if placeholder
     (void) closefrm(table, 1);			// close file
-  DBUG_VOID_RETURN;
-}
-
-/*
-  Remove table from the open table cache
-
-  SYNOPSIS
-    free_cache_entry()
-    table		Table to remove
-
-  NOTE
-    We need to have a lock on LOCK_open when calling this
-*/
-
-static void free_cache_entry(Table_cache *tc, TABLE *table)
-{
-  DBUG_ENTER("free_cache_entry");
-
-  /* This should be done before releasing table share. */
-  table_el_remove_table(tc, table);
-
-  intern_close_table(table);
-
   my_free(table);
   DBUG_VOID_RETURN;
 }
 
+
 /* Free resources allocated by filesort() and read_record() */
 
 void free_io_cache(TABLE *table)
@@ -1163,47 +1407,57 @@ void free_io_cache(TABLE *table)
   DBUG_VOID_RETURN;
 }
 
+
 /**
    Auxiliary function which allows to kill delayed threads for
    particular table identified by its share.
 
    @param share Table share.
 
-   @pre Caller should have LOCK_open mutex.
+   @pre Caller should own locks on all Table_cache instances.
 */
 
 static void kill_delayed_threads_for_table(TABLE_SHARE *share)
 {
-  Table_cache_element *el;
-  uint i;
+  Table_cache::assert_owner_all();
 
-  for (i= 0; i < table_cache_instances; i++)
+  Table_cache_iterator it(share);
+  TABLE *tab;
+
+  while ((tab= it++))
   {
-    if ((el= (Table_cache_element*) my_hash_search(&table_cache[i].cache,
-                                       (uchar*) share->table_cache_key.str,
-                                       share->table_cache_key.length)))
-    {
-      Table_cache_element::TABLE_list::Iterator it(el->used_tables);
-      TABLE *tab;
+    THD *in_use= tab->in_use;
 
-      while ((tab= it++))
+    if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
+        ! in_use->killed)
+    {
+      in_use->killed= THD::KILL_CONNECTION;
+      mysql_mutex_lock(&in_use->mysys_var->mutex);
+      if (in_use->mysys_var->current_cond)
       {
-        THD *in_use= tab->in_use;
-
-        if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
-            ! in_use->killed)
-        {
-          in_use->killed= THD::KILL_CONNECTION;
-          mysql_mutex_lock(&in_use->mysys_var->mutex);
-          if (in_use->mysys_var->current_cond)
-          {
-            mysql_mutex_lock(in_use->mysys_var->current_mutex);
-            mysql_cond_broadcast(in_use->mysys_var->current_cond);
-            mysql_mutex_unlock(in_use->mysys_var->current_mutex);
-          }
-          mysql_mutex_unlock(&in_use->mysys_var->mutex);
-        }
+        mysql_mutex_lock(in_use->mysys_var->current_mutex);
+        mysql_cond_broadcast(in_use->mysys_var->current_cond);
+        mysql_mutex_unlock(in_use->mysys_var->current_mutex);
       }
+      mysql_mutex_unlock(&in_use->mysys_var->mutex);
+    }
+  }
+}
+
+
+/** Free all unused TABLE objects in the table cache. */
+
+void Table_cache::free_all_unused_tables()
+{
+  Table_cache::assert_owner_all();
+
+  for (uint i= 0; i < table_cache_instances; i++)
+  {
+    while (m_table_cache[i].m_unused_tables)
+    {
+      TABLE *table_to_free= m_table_cache[i].m_unused_tables;
+      m_table_cache[i].remove_table(table_to_free);
+      intern_close_table(table_to_free);
     }
   }
 }
@@ -1234,13 +1488,10 @@ bool close_cached_tables(THD *thd, TABLE
   bool result= FALSE;
   bool found= TRUE;
   struct timespec abstime;
-  uint i;
-  uint free_cache_entries= 0;
   DBUG_ENTER("close_cached_tables");
   DBUG_ASSERT(thd || (!wait_for_refresh && !tables));
 
-  lock_all_table_caches();
-  mysql_mutex_lock(&LOCK_open);
+  Table_cache::lock_all();
   if (!tables)
   {
     /*
@@ -1260,15 +1511,7 @@ bool close_cached_tables(THD *thd, TABLE
       Get rid of all unused TABLE and TABLE_SHARE instances. By doing
       this we automatically close all tables which were marked as "old".
     */
-    for (i= 0; i < table_cache_instances; i++)
-    {
-      Table_cache *tc= &table_cache[i];
-      while (tc->unused_tables)
-      {
-        free_cache_entry(tc, tc->unused_tables);
-        free_cache_entries++;
-      }
-    }
+    Table_cache::free_all_unused_tables();
     /* Free table shares which were not freed implicitly by loop above. */
     while (oldest_unused_share->next)
       (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
@@ -1293,8 +1536,7 @@ bool close_cached_tables(THD *thd, TABLE
       wait_for_refresh=0;			// Nothing to wait for
   }
 
-  mysql_mutex_unlock(&LOCK_open);
-  unlock_all_table_caches();
+  Table_cache::unlock_all();
 
   if (!wait_for_refresh)
     DBUG_RETURN(result);
@@ -1583,7 +1825,7 @@ static void close_open_tables(THD *thd)
   DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables));
 
   while (thd->open_tables)
-    (void) close_thread_table(thd, &thd->open_tables);
+    close_thread_table(thd, &thd->open_tables);
 }
 
 
@@ -1798,12 +2040,9 @@ void close_thread_tables(THD *thd)
 
 /* move one table to free list */
 
-bool close_thread_table(THD *thd, TABLE **table_ptr)
+void close_thread_table(THD *thd, TABLE **table_ptr)
 {
-  Table_cache *tc;
-  bool found_old_table= 0;
   TABLE *table= *table_ptr;
-  uint free_cache_entries= 0;
   DBUG_ENTER("close_thread_table");
   DBUG_ASSERT(table->key_read == 0);
   DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
@@ -1835,37 +2074,23 @@ bool close_thread_table(THD *thd, TABLE
   if (table->file != NULL)
     table->file->unbind_psi();
 
-  tc= get_table_cache(thd->thread_id);
-  lock_table_cache(thd->thread_id);
+  Table_cache *tc= Table_cache::get_cache(thd);
+
+  tc->lock();
 
   if (table->s->has_old_version() || table->needs_reopen() ||
       table_def_shutdown_in_progress)
   {
+    tc->remove_table(table);
     mysql_mutex_lock(&LOCK_open);
-    free_cache_entry(tc, table);
+    intern_close_table(table);
     mysql_mutex_unlock(&LOCK_open);
-    free_cache_entries++;
-    found_old_table= 1;
   }
   else
-  {
-    DBUG_ASSERT(table->file);
-    table_el_unuse_table(tc, table->cache_element, table);
-    /*
-      We free the least used table, not the subject table,
-      to keep the LRU order.
-    */
-    if (tc->table_count > table_cache_size_per_instance)
-    {
-      mysql_mutex_lock(&LOCK_open);
-      free_cache_entry(tc, tc->unused_tables);
-      mysql_mutex_unlock(&LOCK_open);
-      free_cache_entries++;
-    }
-  }
-  unlock_table_cache(thd->thread_id);
-  thd->status_var.table_open_cache_overflows+= free_cache_entries;
-  DBUG_RETURN(found_old_table);
+    tc->release_table(thd, table);
+
+  tc->unlock();
+  DBUG_VOID_RETURN;
 }
 
 
@@ -2920,10 +3145,8 @@ bool open_table(THD *thd, TABLE_LIST *ta
   uint flags= ot_ctx->get_flags();
   MDL_ticket *mdl_ticket;
   int error;
-  uint free_cache_entries= 0;
   TABLE_SHARE *share;
   my_hash_value_type hash_value;
-  bool recycled_free_table;
 
   DBUG_ENTER("open_table");
 
@@ -3159,73 +3382,88 @@ bool open_table(THD *thd, TABLE_LIST *ta
 
 retry_share:
   {
-    Table_cache_element *el;
-    Table_cache *tc= get_table_cache(thd->thread_id);
+    Table_cache *tc= Table_cache::get_cache(thd);
+
+    tc->lock();
 
-    lock_table_cache(thd->thread_id);
+    /*
+      Try to get unused TABLE object or at least pointer to
+      TABLE_SHARE from the table cache.
+    */
+    table= tc->get_table(thd, hash_value, key, key_length, &share);
 
-    if ((el= (Table_cache_element*) my_hash_search_using_hash_value(
-                                      &tc->cache,
-                                      hash_value, (uchar*) key, key_length)))
+    if (table)
     {
+      /* We have found an unused TABLE object. */
+
       if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
       {
-        if (el->share->has_old_version())
-        {
-          MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
-          bool wait_result;
-
-          unlock_table_cache(thd->thread_id);
-
-          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();
+        /*
+          TABLE_SHARE::version can only be initialised while holding the
+          LOCK_open and in this case no one has a reference to the share
+          object, if a reference exists to the share object it is necessary
+          to lock both LOCK_open AND all table caches in order to update
+          TABLE_SHARE::version. The same locks are required to increment
+          refresh_version global variable.
+
+          As result it is safe to compare TABLE_SHARE::version and
+          refresh_version values while having only lock on the table
+          cache for this thread.
 
-          if (wait_result)
-            DBUG_RETURN(TRUE);
+          Table cache should not contain any unused TABLE objects with
+          old versions.
+        */
+        DBUG_ASSERT(!share->has_old_version());
 
-          goto retry_share;
-        }
-        /**
-         * share->version can only be initialised while holding the
-         * LOCK_open and in this case no one has a reference to the
-         * share object, if a reference exists to the share object
-         * it is necessary to lock both LOCK_open AND all table
-         * caches in order to update share->version.
-         */
+        /*
+          Still some of already opened might become outdated (e.g. due to
+          concurrent table flush). So we need to compare version of opened
+          tables with version of TABLE object we just have got.
+        */
         if (thd->open_tables &&
-            thd->open_tables->s->version != el->share->version)
+            thd->open_tables->s->version != share->version)
         {
-          unlock_table_cache(thd->thread_id);
+          tc->release_table(thd, table);
+          tc->unlock();
           (void)ot_ctx->request_backoff_action(
-            Open_table_context::OT_REOPEN_TABLES,
-            NULL);
+                          Open_table_context::OT_REOPEN_TABLES,
+                          NULL);
           DBUG_RETURN(TRUE);
         }
       }
+      tc->unlock();
 
-      if (!el->free_tables.is_empty())
-      {
-        table= el->free_tables.front();
-        table_el_use_table(thd, tc, el, table);
-        unlock_table_cache(thd->thread_id);
-        recycled_free_table= true;
-        thd->status_var.table_open_cache_hits++;
-        goto table_found;
-      }
-      else
-      {
-        share= el->share;
-        mysql_mutex_lock(&LOCK_open);
-        unlock_table_cache(thd->thread_id);
-        share->ref_count++;
-        goto share_found;
-      }
+      /* Call rebind_psi outside of the critical section. */
+      DBUG_ASSERT(table->file != NULL);
+      table->file->rebind_psi();
+
+      thd->status_var.table_open_cache_hits++;
+      goto table_found;
+    }
+    else if (share)
+    {
+      /*
+        We weren't able to get an unused TABLE object. Still we have
+        found TABLE_SHARE for it. So let us try to create new TABLE
+        for it. We start by incrementing share's reference count and
+        checking its version.
+      */
+      mysql_mutex_lock(&LOCK_open);
+      tc->unlock();
+      share->ref_count++;
+      goto share_found;
+    }
+    else
+    {
+      /*
+        We have not found neither TABLE nor TABLE_SHARE object in
+        table cache (this means that there are no TABLE objects for
+        it in it).
+        Let us try to get TABLE_SHARE from table definition cache or
+        from disk and then to create TABLE object for it.
+      */
+      tc->unlock();
     }
-    unlock_table_cache(thd->thread_id);
   }
 
   mysql_mutex_lock(&LOCK_open);
@@ -3326,12 +3564,12 @@ share_found:
         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);
 
+      MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
+      bool wait_result;
+
       thd->push_internal_handler(&mdl_deadlock_handler);
       wait_result= tdc_wait_for_old_version(thd, table_list->db,
                                             table_list->table_name,
@@ -3395,51 +3633,21 @@ share_found:
     goto err_lock;
   }
   {
-    Table_cache_element *el;
-    Table_cache *tc= get_table_cache(thd->thread_id);
+    /* Add new TABLE object to table cache for this connection. */
+    Table_cache *tc= Table_cache::get_cache(thd);
 
-    lock_table_cache(thd->thread_id);
-    if (!(el= (Table_cache_element*) my_hash_search_using_hash_value(
-                                  &tc->cache,
-                                  hash_value, (uchar*) key, key_length)))
-    {
-      el= new Table_cache_element(share);
-      if (my_hash_insert(&tc->cache, (uchar*)el))
-      {
-        delete el;
-        unlock_table_cache(thd->thread_id);
-        goto err_lock;
-      }
-    }
-    /* Add table to the used tables list */
-    table_el_add_used_table(thd, tc, el, table);
-    /* We have too many TABLE instances around let us try to get rid of them. */
-    if (tc->table_count > table_cache_size_per_instance &&
-        tc->unused_tables)
+    tc->lock();
+
+    if (tc->add_used_table(thd, table))
     {
-      mysql_mutex_lock(&LOCK_open);
-      while (tc->table_count > table_cache_size_per_instance &&
-             tc->unused_tables)
-      {
-        free_cache_entry(tc, tc->unused_tables);
-        free_cache_entries++;
-      }
-      mysql_mutex_unlock(&LOCK_open);
+      tc->unlock();
+      goto err_lock;
     }
-    unlock_table_cache(thd->thread_id);
+    tc->unlock();
   }
-  recycled_free_table= false;
   thd->status_var.table_open_cache_misses++;
-  thd->status_var.table_open_cache_overflows+= free_cache_entries;
 
 table_found:
-  /* Call rebind_psi outside of the LOCK_open critical section. */
-  if (recycled_free_table)
-  {
-    DBUG_ASSERT(table->file != NULL);
-    table->file->rebind_psi();
-  }
-
   table->mdl_ticket= mdl_ticket;
 
   table->next= thd->open_tables;		/* Link into simple list */
@@ -3447,7 +3655,7 @@ table_found:
 
   table->reginfo.lock_type=TL_READ;		/* Assume read */
 
-reset:
+ reset:
   table->created= TRUE;
   /*
     Check that there is no reference to a condition from an earlier query
@@ -4267,15 +4475,13 @@ static bool auto_repair_table(THD *thd,
   }
   my_free(entry);
 
-  lock_all_table_caches();
-  mysql_mutex_lock(&LOCK_open);
+  Table_cache::lock_all();
   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,
                    TRUE);
-  mysql_mutex_unlock(&LOCK_open);
-  unlock_all_table_caches();
+  Table_cache::unlock_all();
   return result;
 end_unlock:
   mysql_mutex_unlock(&LOCK_open);
@@ -9470,21 +9676,62 @@ my_bool mysql_rm_tmp_tables(void)
 
 void tdc_flush_unused_tables()
 {
-  uint i;
-  for (i= 0; i < table_cache_instances; i++)
+  Table_cache::lock_all();
+  Table_cache::free_all_unused_tables();
+  Table_cache::unlock_all();
+}
+
+
+/**
+   Remove all or some (depending on parameter) TABLE objects for
+   the table from all table cache instances.
+
+   @param  thd          Thread context
+   @param  remove_type  Type of removal. @sa tdc_remove_table().
+   @param  key          Key identifying table.
+   @param  key_length   Length of key.
+
+   TODO: Find better name? free_something() ?
+
+   @note Caller should own LOCK_open and locks on all table cache
+         instances.
+*/
+void Table_cache::remove_table_all(THD *thd,
+                                   enum_tdc_remove_table_type remove_type,
+                                   const char *key, uint key_length)
+{
+  Table_cache::assert_owner_all();
+
+  for (uint i= 0; i < table_cache_instances; i++)
   {
-    mysql_mutex_lock(&table_cache[i].lock);
-    mysql_mutex_lock(&LOCK_open);
-    while (table_cache[i].unused_tables)
+    Table_cache_element *el;
+
+    if ((el= (Table_cache_element*)my_hash_search(&m_table_cache[i].m_cache,
+                                                  (uchar*) key, key_length)))
     {
-      /**
-       * We will ignore to keep track of removed unused tables here
-       * since we don't have a THD object to report it on.
-       */
-      free_cache_entry(&table_cache[i], table_cache[i].unused_tables);
+      Table_cache_element::TABLE_list::Iterator it(el->free_tables);
+      TABLE *table;
+
+#ifndef DBUG_OFF
+      if (remove_type == TDC_RT_REMOVE_ALL)
+        DBUG_ASSERT(el->used_tables.is_empty());
+      else if (remove_type == TDC_RT_REMOVE_NOT_OWN)
+      {
+        Table_cache_element::TABLE_list::Iterator it2(el->used_tables);
+        while ((table= it2++))
+        {
+          if (table->in_use != thd)
+            DBUG_ASSERT(0);
+        }
+      }
+#endif
+
+      while ((table= it++))
+      {
+        m_table_cache[i].remove_table(table);
+        intern_close_table(table);
+      }
     }
-    mysql_mutex_unlock(&LOCK_open);
-    mysql_mutex_unlock(&table_cache[i].lock);
   }
 }
 
@@ -9525,22 +9772,12 @@ void tdc_remove_table(THD *thd, enum_tdc
 {
   char key[MAX_DBKEY_LENGTH];
   uint key_length;
-  TABLE *table;
   TABLE_SHARE *share;
-  uint i;
-  uint free_cache_entries= 0;
 
   if (! has_lock)
-  {
-    lock_all_table_caches();
-    mysql_mutex_lock(&LOCK_open);
-  }
+    Table_cache::lock_all();
   else
-  {
-    mysql_mutex_assert_owner(&LOCK_open);
-    for (i= 0; i < table_cache_instances; i++)
-      mysql_mutex_assert_owner(&table_cache[i].lock);
-  }
+    Table_cache::assert_owner_all();
 
   DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED ||
               thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name,
@@ -9565,51 +9802,14 @@ void tdc_remove_table(THD *thd, enum_tdc
         used.
       */
       share->version= 0;
-      for (i= 0; i < table_cache_instances; i++)
-      {
-        Table_cache_element *el;
-
-        if ((el= (Table_cache_element*)my_hash_search(&table_cache[i].cache,
-                                                      (uchar*) key,
-                                                      key_length)))
-        {
-          Table_cache_element::TABLE_list::Iterator it(el->free_tables);
-#ifndef DBUG_OFF
-          if (remove_type == TDC_RT_REMOVE_ALL)
-          {
-            DBUG_ASSERT(el->used_tables.is_empty());
-          }
-          else if (remove_type == TDC_RT_REMOVE_NOT_OWN)
-          {
-            Table_cache_element::TABLE_list::Iterator it2(el->used_tables);
-            while ((table= it2++))
-            {
-              if (table->in_use != thd)
-              {
-                DBUG_ASSERT(0);
-              }
-            }
-          }
-#endif
-
-          while ((table= it++))
-          {
-            free_cache_entry(&table_cache[i], table);
-            free_cache_entries++;
-          }
-        }
-      }
+      Table_cache::remove_table_all(thd, remove_type, key, key_length);
     }
     else
       (void) my_hash_delete(&table_def_cache, (uchar*) share);
   }
 
   if (! has_lock)
-  {
-    mysql_mutex_unlock(&LOCK_open);
-    unlock_all_table_caches();
-  }
-  thd->status_var.table_open_cache_overflows+= free_cache_entries;
+    Table_cache::unlock_all();
 }
 
 

=== modified file 'sql/sql_base.h'
--- a/sql/sql_base.h	2012-05-04 07:13:43 +0000
+++ b/sql/sql_base.h	2012-05-11 16:05:27 +0000
@@ -76,7 +76,6 @@ bool table_def_init(void);
 void table_def_free(void);
 void table_def_start_shutdown(void);
 void assign_new_table_id(TABLE_SHARE *share);
-uint cached_open_tables(void);
 uint cached_table_definitions(void);
 uint get_table_def_key(const TABLE_LIST *table_list, const char **key);
 TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list,
@@ -252,7 +251,7 @@ bool open_normal_and_derived_tables(THD
 bool lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags);
 void free_io_cache(TABLE *entry);
 void intern_close_table(TABLE *entry);
-bool close_thread_table(THD *thd, TABLE **table_ptr);
+void close_thread_table(THD *thd, TABLE **table_ptr);
 bool close_temporary_tables(THD *thd);
 TABLE_LIST *unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list,
                          bool check_alias);
@@ -298,7 +297,6 @@ TABLE *find_table_for_mdl_upgrade(THD *t
 void mark_tmp_table_for_reuse(TABLE *table);
 bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool *exists);
 
-extern TABLE *unused_tables;
 extern Item **not_found_item;
 extern Field *not_found_field;
 extern Field *view_ref_found;
@@ -611,17 +609,24 @@ private:
   int m_unhandled_errors;
 };
 
+
+/**
+  Element that represents the table in the specific table cache.
+  Plays for table cache instance role similar to role of TABLE_SHARE
+  for table definition cache.
+*/
+
 struct Table_cache_element
 {
   /*
-   * Doubly-linked (back-linked) lists of used and unused TABLE objects
-   * for this table in this table cache (one such list per table cache).
-   * The table cache element represents the table in each table cache.
-   */
+    Doubly-linked (back-linked) lists of used and unused TABLE objects
+    for this table in this table cache (one such list per table cache).
+  */
   typedef I_P_List <TABLE,
                     I_P_List_adapter<TABLE,
-                                     &TABLE::share_next,
-                                     &TABLE::share_prev> > TABLE_list;
+                                     &TABLE::cache_next,
+                                     &TABLE::cache_prev> > TABLE_list;
+
   TABLE_list used_tables;
   TABLE_list free_tables;
   TABLE_SHARE *share;
@@ -632,53 +637,157 @@ struct Table_cache_element
   }
 };
 
-struct Table_cache
+
+/**
+  Cache for open TABLE objects.
+
+  The idea behind this cache is that most statements don't need to
+  go to a central table definition cache to get a TABLE object and
+  therefore don't need to lock LOCK_open mutex.
+  Instead they only need to go to one Table_cache instance (the
+  specific instance is determined by thread id) and only lock the
+  mutex protecting this cache.
+  DDL statements that need to remove all TABLE objects from all caches
+  need to lock mutexes for all Table_cache instances, but they are rare.
+
+  This significantly increases scalability in some scenarios.
+*/
+
+class Table_cache
 {
-  HASH cache;
+private:
+  /**
+    The table cache lock protects the following data:
+
+    1) m_unused_tables list.
+    2) m_cache hash.
+    3) used_tables, free_tables lists in Table_cache_element objects in
+       this cache.
+    4) the element in TABLE_SHARE::cache_element[] array that corresponds
+       to this cache,
+    5) in_use member in TABLE object.
+    6) Also ownership of mutexes for all caches are required to update
+       the refresh_version and table_def_shutdown_in_progress variables
+       and TABLE_SHARE::version member.
+
+    The intention is that any query that finds a cached table object in
+    its designated table cache should only need to lock this mutex
+    instance and there should be no need to lock LOCK_open. LOCK_open is
+    still required however to create and release TABLE objects. However
+    most usage of the MySQL Server should be able to set the cache size
+    big enough so that the majority of the queries only need to lock this
+    mutex instance and not LOCK_open.
+  */
+  mysql_mutex_t m_lock;
+
+  /**
+    The hash of Table_cache_element objects, each table/table share that
+    has any TABLE object in the Table_cache has a Table_cache_element from
+    which the list of free TABLE objects in this table cache AND the list
+    of used TABLE objects in this table cache is stored.
+    We use Table_cache_element::share::table_cache_key as key for this hash.
+  */
+  HASH m_cache;
+
   /**
-   * List that contains all TABLE instances for tables in this particular
-   * table cache that are in not use by any thread. Recently used TABLE
-   * instances are appended to the end of the list. Thus the beginning of
-   * the list contains which have been least recently used.
-   */
-  TABLE *unused_tables;
-
-  /**
-   * Total number of TABLE instances for tables in this particular table
-   * cache (both in use by threads and not in use).
-   * This value summed over all table caches is accessible to users as
-   * Open_tables status variable.
-   */
-  uint table_count;
-  /**
-   * The table cache lock protects the following variables.
-   * 1) unused_tables
-   * 2) cache (the hash of Table_cache_element, each table share that
-   *    has a table object in the Table_cache has a Table_cache_element
-   *    from which the list of free TABLE objects in this table cache
-   *    AND the list of used TABLE objects in this table cache is stored.
-   * 3) used_tables, free_tables in Table_cache_element object
-   * 4) Also all these mutexes are required to update the:
-   *    refresh_version and share->version variables.
-   * 
-   * The intention is that any query that finds a cached table object in
-   * its designated table cache should only need to lock this mutex
-   * instance and there should be no need to lock LOCK_open. LOCK_open is
-   * still required however to create and release TABLE objects. However
-   * most usage of the MySQL Server should be able to set the cache size
-   * big enough so that the majority of the queries only need to lock this
-   * mutex instance and not LOCK_open.
-   */
-  mysql_mutex_t lock;
+    List that contains all TABLE instances for tables in this particular
+    table cache that are in not use by any thread. Recently used TABLE
+    instances are appended to the end of the list. Thus the beginning of
+    the list contains which have been least recently used.
+  */
+  TABLE *m_unused_tables;
+
+  /**
+    Total number of TABLE instances for tables in this particular table
+    cache (both in use by threads and not in use).
+    This value summed over all table caches is accessible to users as
+    Open_tables status variable.
+  */
+  uint m_table_count;
+
+  /**
+    An array of Table_cache instances.
+    Only the first table_cache_instances elements in it are used.
+  */
+  static Table_cache m_table_cache[MAX_TABLE_CACHES];
+
+private:
 
   bool init();
   void destroy();
+
 #ifdef EXTRA_DEBUG
-  check_unused_links(void);
+  void check_unused();
+#else
+  void check_unused() {}
 #endif
+  inline void link_unused_table(TABLE *table);
+  inline void unlink_unused_table(TABLE *table);
+
+  inline void free_unused_tables_if_necessary(THD *thd);
+
+  uint cache_index() const { return (this - &m_table_cache[0]); }
+
+#ifndef DBUG_OFF
+  void print_tables();
+#endif
+
+public:
+
+  static bool init_all();
+  static void destroy_all();
+
+  static Table_cache *get_cache(THD *thd);
+
+  static uint cached_tables();
+
+  static void lock_all();
+  static void unlock_all();
+  static void assert_owner_all();
+
+  inline void lock();
+  inline void unlock();
+
+  TABLE* get_table(THD *thd, my_hash_value_type hash_value,
+                   const char *key, uint key_length,
+                   TABLE_SHARE **share);
+
+  void release_table(THD *thd, TABLE *table);
+
+  bool add_used_table(THD *thd, TABLE *table);
+  void remove_table(TABLE *table);
+
+  static void remove_table_all(THD *thd,
+                               enum_tdc_remove_table_type remove_type,
+                               const char *key, uint key_length);
+
+  static void free_all_unused_tables();
+
+#ifndef DBUG_OFF
+  static void print_tables_all();
+#endif
+
+  friend class Table_cache_iterator;
+};
+
+
+/**
+  Iterator which allows to go through all used TABLE instances
+  for the table in all table caches.
+*/
+
+class Table_cache_iterator
+{
+  const TABLE_SHARE *share;
+  uint current_cache_index;
+  TABLE *current_table;
+
+  void move_to_next_table();
+
+public:
+  Table_cache_iterator(const TABLE_SHARE *share);
+  TABLE* operator++(int);
+  void rewind();
 };
 
-Table_cache* get_table_cache(uint thread_id);
-void lock_all_table_caches(void);
-void unlock_all_table_caches(void);
 #endif /* SQL_BASE_INCLUDED */

=== modified file 'sql/sql_const.h'
--- a/sql/sql_const.h	2012-02-27 10:20:18 +0000
+++ b/sql/sql_const.h	2012-05-11 16:05:27 +0000
@@ -127,6 +127,8 @@
   cache can contain at least all tables of a given statement.
 */
 #define TABLE_DEF_CACHE_MIN     400
+/** Maximum supported number of table cache instances. */
+#define MAX_TABLE_CACHES        64
 
 /*
   Stack reservation.

=== modified file 'sql/sql_handler.cc'
--- a/sql/sql_handler.cc	2012-04-23 08:44:47 +0000
+++ b/sql/sql_handler.cc	2012-05-11 16:05:27 +0000
@@ -134,7 +134,7 @@ static void mysql_ha_close_table(THD *th
     /* Non temporary table. */
     tables->table->file->ha_index_or_rnd_end();
     tables->table->open_by_handler= 0;
-    (void) close_thread_table(thd, &tables->table);
+    close_thread_table(thd, &tables->table);
     thd->mdl_context.release_lock(tables->mdl_request.ticket);
   }
   else if (tables->table)

=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc	2012-04-21 12:11:15 +0000
+++ b/sql/sql_parse.cc	2012-05-11 16:05:27 +0000
@@ -1569,7 +1569,7 @@ bool dispatch_command(enum enum_server_c
                         current_global_status_var.long_query_count,
                         current_global_status_var.opened_tables,
                         refresh_version,
-                        cached_open_tables(),
+                        Table_cache::cached_tables(),
                         (uint) (queries_per_second1000 / 1000),
                         (uint) (queries_per_second1000 % 1000));
 #ifdef EMBEDDED_LIBRARY

=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc	2012-04-25 15:46:11 +0000
+++ b/sql/sql_table.cc	2012-05-11 16:05:27 +0000
@@ -7564,10 +7564,7 @@ end_inplace:
                                  alter_ctx.new_db, alter_ctx.new_name,
                                  false, true);
     if (t_table)
-    {
       intern_close_table(t_table);
-      my_free(t_table);
-    }
     else
       sql_print_warning("Could not open table %s.%s after rename\n",
                         alter_ctx.new_db, alter_ctx.table_name);

=== modified file 'sql/sql_test.cc'
--- a/sql/sql_test.cc	2012-04-25 07:38:34 +0000
+++ b/sql/sql_test.cc	2012-05-11 16:05:27 +0000
@@ -82,73 +82,80 @@ print_where(Item *cond,const char *info,
 
 static void print_cached_tables(void)
 {
-  uint i,idx,count,unused;
-  uint cached_tables;
-  TABLE *start_link, *lnk, *entry;
-  Table_cache_element *el;
-  Table_cache *cache;
 
   compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions));
 
   /* purecov: begin tested */
-  lock_all_table_caches();
-  mysql_mutex_lock(&LOCK_open);
-  cached_tables= cached_open_tables();
+  Table_cache::lock_all();
+
+  Table_cache::print_tables_all();
+
+  printf("\nCurrent refresh version: %ld\n",refresh_version);
+  if (my_hash_check(&table_def_cache))
+    printf("Error: Table definition hash table is corrupted\n");
+  fflush(stdout);
+  Table_cache::unlock_all();
+  /* purecov: end */
+  return;
+}
+
+
+void Table_cache::print_tables_all()
+{
   puts("DB             Table                            Version  Thread  Open  Lock");
 
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].print_tables();
+}
+
+
+void Table_cache::print_tables()
+{
+  uint idx,count,unused;
+  TABLE *start_link, *lnk, *entry;
+  Table_cache_element *el;
   unused= 0;
   count=0;
-  for (i= 0; i < table_cache_instances; i++)
+
+  for (idx= 0; idx < m_cache.records; idx++)
   {
-    cache= get_table_cache(i);
-    for (idx= 0; idx < cache->cache.records; idx++)
-    {
-      el= (Table_cache_element*) my_hash_element(&cache->cache, idx);
-      Table_cache_element::TABLE_list::Iterator it(el->used_tables);
+    el= (Table_cache_element*) my_hash_element(&m_cache, idx);
+    Table_cache_element::TABLE_list::Iterator it(el->used_tables);
 
-      while ((entry= it++))
-      {
-        printf("%-14.14s %-32s%6ld%8ld%6d  %s\n",
-               entry->s->db.str, entry->s->table_name.str, entry->s->version,
-               entry->in_use->thread_id, entry->db_stat ? 1 : 0,
-               lock_descriptions[(int)entry->reginfo.lock_type]);
-      }
-      it.init(el->free_tables);
-      while ((entry= it++))
-      {
-        unused++;
-        printf("%-14.14s %-32s%6ld%8ld%6d  %s\n",
-               entry->s->db.str, entry->s->table_name.str, entry->s->version,
-               0L, entry->db_stat ? 1 : 0, "Not in use");
-      }
+    while ((entry= it++))
+    {
+      printf("%-14.14s %-32s%6ld%8ld%6d  %s\n",
+             entry->s->db.str, entry->s->table_name.str, entry->s->version,
+             entry->in_use->thread_id, entry->db_stat ? 1 : 0,
+             lock_descriptions[(int)entry->reginfo.lock_type]);
     }
-    if ((start_link=lnk=cache->unused_tables))
+    it.init(el->free_tables);
+    while ((entry= it++))
     {
-      do
-      {
-        if (lnk != lnk->next->prev || lnk != lnk->prev->next)
-        {
-	  printf("unused_links isn't linked properly\n");
-	  return;
-        }
-      } while (count++ < cached_tables && (lnk=lnk->next) != start_link);
-      if (lnk != start_link)
+      unused++;
+      printf("%-14.14s %-32s%6ld%8ld%6d  %s\n",
+             entry->s->db.str, entry->s->table_name.str, entry->s->version,
+             0L, entry->db_stat ? 1 : 0, "Not in use");
+    }
+  }
+  if ((start_link= lnk= m_unused_tables))
+  {
+    do
+    {
+      if (lnk != lnk->next->prev || lnk != lnk->prev->next)
       {
-        printf("Unused_links aren't connected\n");
+	printf("unused_links isn't linked properly\n");
+	return;
       }
+    } while (count++ < m_table_count && (lnk=lnk->next) != start_link);
+    if (lnk != start_link)
+    {
+      printf("Unused_links aren't connected\n");
     }
   }
   if (count != unused)
     printf("Unused_links (%d) doesn't match table_def_cache: %d\n", count,
            unused);
-  printf("\nCurrent refresh version: %ld\n",refresh_version);
-  if (my_hash_check(&table_def_cache))
-    printf("Error: Table definition hash table is corrupted\n");
-  fflush(stdout);
-  mysql_mutex_unlock(&LOCK_open);
-  unlock_all_table_caches();
-  /* purecov: end */
-  return;
 }
 
 
@@ -444,7 +451,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), cached_open_tables() + 20,50);
+  (void) my_init_dynamic_array(&saved_table_locks,sizeof(TABLE_LOCK_INFO), Table_cache::cached_tables() + 20,50);
   mysql_mutex_lock(&THR_LOCK_lock);
   for (list= thr_lock_thread_list; list; list= list_rest(list))
   {
@@ -566,7 +573,7 @@ Open tables:   %10lu\n\
 Open files:    %10lu\n\
 Open streams:  %10lu\n",
 	 (ulong) tmp.opened_tables,
-	 (ulong) cached_open_tables(),
+	 (ulong) Table_cache::cached_tables(),
 	 (ulong) my_file_opened,
 	 (ulong) my_stream_opened);
 

=== modified file 'sql/sys_vars.cc'
--- a/sql/sys_vars.cc	2012-04-25 07:09:46 +0000
+++ b/sql/sys_vars.cc	2012-05-11 16:05:27 +0000
@@ -2713,6 +2713,11 @@ static Sys_var_ulong Sys_table_def_size(
        VALID_RANGE(TABLE_DEF_CACHE_MIN, 512*1024),
        DEFAULT(TABLE_DEF_CACHE_DEFAULT), BLOCK_SIZE(1));
 
+/*
+  TODO/FIXME: Ensure that table_cache_size_per_instance is properly
+              updated when table_cache_size is changed dynamically.
+*/
+
 static Sys_var_ulong Sys_table_cache_size(
        "table_open_cache", "The number of cached open tables",
        GLOBAL_VAR(table_cache_size), CMD_LINE(REQUIRED_ARG),
@@ -2722,7 +2727,7 @@ static Sys_var_ulong Sys_table_cache_siz
 static Sys_var_ulong Sys_table_cache_instances(
        "table_open_cache_instances", "The number of table cache instances",
        READ_ONLY GLOBAL_VAR(table_cache_instances), CMD_LINE(REQUIRED_ARG),
-       VALID_RANGE(1, 64), DEFAULT(1),
+       VALID_RANGE(1, MAX_TABLE_CACHES), DEFAULT(1),
        BLOCK_SIZE(1));
 
 static Sys_var_ulong Sys_thread_cache_size(

=== modified file 'sql/table.cc'
--- a/sql/table.cc	2012-04-20 12:37:01 +0000
+++ b/sql/table.cc	2012-05-11 16:05:27 +0000
@@ -3210,6 +3210,7 @@ uint Wait_for_flush::get_deadlock_weight
   return m_deadlock_weight;
 }
 
+
 /**
   Traverse portion of wait-for graph which is reachable through this
   table share in search for deadlocks.
@@ -3220,6 +3221,8 @@ uint Wait_for_flush::get_deadlock_weight
   @retval TRUE  A deadlock is found. A victim is remembered
                 by the visitor.
   @retval FALSE No deadlocks, it's OK to begin wait.
+
+  QQ: Should we move this method to sql_base.cc to allow inlining?
 */
 
 bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush,
@@ -3229,7 +3232,6 @@ bool TABLE_SHARE::visit_subgraph(Wait_fo
   MDL_context *src_ctx= wait_for_flush->get_ctx();
   bool result= TRUE;
   bool locked= FALSE;
-  uint i;
 
   /*
     To protect used_tables list from being concurrently modified
@@ -3241,9 +3243,11 @@ bool TABLE_SHARE::visit_subgraph(Wait_fo
   if (gvisitor->m_lock_open_count++ == 0)
   {
     locked= TRUE;
-    lock_all_table_caches();
-    mysql_mutex_lock(&LOCK_open);
+    Table_cache::lock_all();
   }
+
+  Table_cache_iterator tables_it(this);
+
   /*
     In case of multiple searches running in parallel, avoid going
     over the same loop twice and shortcut the search.
@@ -3254,36 +3258,24 @@ bool TABLE_SHARE::visit_subgraph(Wait_fo
     result= FALSE;
     goto end;
   }
+
   if (gvisitor->enter_node(src_ctx))
     goto end;
 
-  for (i= 0; i < table_cache_instances; i++)
+  while ((table= tables_it++))
   {
-    Table_cache_element *el;
-
-    if ((el= (Table_cache_element*)my_hash_search(
-                                   (const HASH*)get_table_cache(i),
-                                   (uchar*)table_cache_key.str,
-                                   table_cache_key.length)))
+    if (gvisitor->inspect_edge(&table->in_use->mdl_context))
     {
-      Table_cache_element::TABLE_list::Iterator tables_it(el->used_tables);
+      goto end_leave_node;
+    }
+  }
 
-      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;
-        }
-      }
+  tables_it.rewind();
+  while ((table= tables_it++))
+  {
+    if (table->in_use->mdl_context.visit_subgraph(gvisitor))
+    {
+      goto end_leave_node;
     }
   }
 
@@ -3296,14 +3288,14 @@ end:
   gvisitor->m_lock_open_count--;
   if (locked)
   {
-    assert(gvisitor->m_lock_open_count == 0);
-    mysql_mutex_unlock(&LOCK_open);
-    unlock_all_table_caches();
+    DBUG_ASSERT(gvisitor->m_lock_open_count == 0);
+    Table_cache::unlock_all();
   }
 
   return result;
 }
 
+
 /**
   Wait until the subject share is removed from the table
   definition cache and make sure it's destroyed.

=== modified file 'sql/table.h'
--- a/sql/table.h	2012-05-04 07:13:43 +0000
+++ b/sql/table.h	2012-05-11 16:05:27 +0000
@@ -494,8 +494,6 @@ TABLE_CATEGORY get_table_category(const
                                   const LEX_STRING *name);
 
 
-struct TABLE_table_cache;
-
 extern ulong refresh_version;
 
 typedef struct st_table_field_type
@@ -586,6 +584,13 @@ struct TABLE_SHARE
   TYPELIB *intervals;			/* pointer to interval info */
   mysql_mutex_t LOCK_ha_data;           /* To protect access to ha_data */
   TABLE_SHARE *next, **prev;            /* Link to unused shares */
+  /**
+    Elements of table cache respresenting this table in each of Table_cache
+    instances. Each element of the array is protected by Table_cache::m_lock
+    in the corresponding Table_cache. False sharing should not be a problem
+    in this case as elements of this array are supposed to be updated rarely.
+  */
+  Table_cache_element *cache_element[MAX_TABLE_CACHES];
 
   /* The following is copied to each TABLE on OPEN */
   Field **field;
@@ -896,21 +901,27 @@ struct TABLE
   virtual ~TABLE() {}
 
   TABLE_SHARE	*s;
+  /**
+    Element of table cache respresenting table and linking together
+    all TABLE instances for the table in particular Table_cache.
+  */
   Table_cache_element *cache_element;
   handler	*file;
   TABLE *next, *prev;
 
 private:
   /**
-     Links for the lists of used/unused TABLE objects for this share.
+     Links for the lists of used/unused TABLE objects for the particular
+     table in the specific instance of Table_cache (in other words for
+     specific Table_cache_element object).
      Declared as private to avoid direct manipulation with those objects.
      One should use methods of I_P_List template instead.
   */
-  TABLE *share_next, **share_prev;
+  TABLE *cache_next, **cache_prev;
 
   /*
     Give Table_cache_element access to the above two members to allow
-    using them for linking TABLE objects in list.
+    using them for linking TABLE objects in a list.
   */
   friend struct Table_cache_element;
 

No bundle (reason: useless for push emails).
Thread
bzr push into mysql-trunk branch (Dmitry.Lenev:3891 to 3892) WL#5772Dmitry Lenev21 May