List:Commits« Previous MessageNext Message »
From:Dmitry Lenev Date:May 21 2012 5:57pm
Subject:bzr push into mysql-trunk branch (Dmitry.Lenev:3898 to 3899) WL#5772
View as plain text  
 3899 Dmitry Lenev	2012-05-21
      WL#5772 "Add partitioned Table Definition Cache to avoid using
      LOCK_open and its derivatives in DML queries".
      
      Re-introduced table cache - a separate cache holding TABLE objects
      ready for use by statements protected by its own mutex. In the most
      common case a connection opening/closing a table only needs to go
      to this cache for getting/releasing TABLE object and do not need
      to go to the table definition cache. By having several instances of
      such cache (each protected by its own lock) and partitioning
      user connections between these instances (based on connection id)
      we can greatly improve scalability in some scenarios.
      
      Added new start-up parameter --table_open_cache_instances for
      setting number of table cache instances in the system.
      
      Existing --table_open_cache parameter now limits total number of
      TABLE object in all table cache instances combined.
      
      Added Table_open_cache_hits, Table_open_cache_misses,
      Table_open_cache_overflows - per-connection and global status
      variables for tracking and tuning performance of new cache.
      (The latter allows to track how often we have to expel tables
      from the cache since the table cache instance contains more
      than table_open_cache/table_open_cache_instances objects.)
     @ mysql-test/r/mysqld--help-notwin.result
        Adjusted test results after adding new --table-open-cache-instances
        parameter.
     @ mysql-test/r/mysqld--help-win.result
        Adjusted test results after adding new --table-open-cache-instances
        parameter.
     @ mysql-test/r/status.result
        Added test coverage for status variables added by WL#5772 "Add
        partitioned Table Definition Cache to avoid using LOCK_open and
        its derivatives in DML queries".
     @ mysql-test/suite/perfschema/r/func_mutex.result
        Adjusted test case to use mutex for table cache instance instead
        of LOCK_open. The latter is no longer locked in the common case,
        when table is present in table cache.
     @ mysql-test/suite/perfschema/t/func_mutex.test
        Adjusted test case to use mutex for table cache instance instead
        of LOCK_open. The latter is no longer locked in the common case,
        when table is present in table cache.
     @ mysql-test/suite/sys_vars/r/table_open_cache_instances_basic.result
        Added test coverage for new table_open_cache_instances
        global variable.
     @ mysql-test/suite/sys_vars/t/table_open_cache_instances_basic.test
        Added test coverage for new table_open_cache_instances
        global variable.
     @ mysql-test/t/status.test
        Added test coverage for status variables added by WL#5772 "Add
        partitioned Table Definition Cache to avoid using LOCK_open and
        its derivatives in DML queries".
     @ sql/mysqld.cc
        Added global variable for table_cache_instances parameter. Also
        added global table_cache_size_per_instance variable for size of
        single instance of table cache.
        Added new status variables for table cache - Table_open_cache_hits,
        Table_open_cache_misses, Table_open_cache_overflows.
     @ sql/mysqld.h
        Added global variable for table_cache_instances parameter. Also
        added global table_cache_size_per_instance variable for size of
        single instance of table cache.
     @ sql/sql_base.cc
        Implemented a table cache:
        * Added Table_cache object representing a table cache instance.
          Made counter of TABLE objects and list of unused TABLE objects
          members of this class, protected by its lock.
        * Introduced Table_cache_element, a structure representing table
          in the specific table cache instances. Moved lists of used and
          unused TABLE instances for the table from TABLE_SHARE to this
          structure.
        * Made table_def_add_used_table(), table_def_remove_table(),
          table_def_use_table() and table_def_unuse_table() methods of
          Table_cache class.
        * Moved common code trimming list of unused tables to auxiliary
          method in this class. Moved all code dealing with global list
          of unused tables to methods of Table_cache.
        * Introduced Table_cache_manager class - a container for all
          Table_cache instances in the system and incapsulating operations
          on all of them.
        * Changed code in open_table() to try to get TABLE object from
          appropriate Table_cache instance, before going to the table
          definition cache for TABLE_SHARE and creating a TABLE object
          based on it.
        * Changed close_thread_table() to deal with Table_cache instead
          of table definition cache.
        * Changed code that needs to deal with all TABLE instances for
          a table to acquire locks on all table caches.
        * Introduced Table_cache_iterator, an iterator over all used TABLE
          instances for the table. Changed code that earlier used iterator
          over TABLE_SHARE::used_tables to use this new iterator.
        * Moved code dealing with lists of used and unused TABLE objects
          from tdc_remove_table() to corresponding method in
          Table_cache_manager.
     @ sql/sql_base.h
        Implemented a table cache:
        * Added Table_cache object representing a table cache instance.
        * Introduced Table_cache_manager class, a container for all
          Table_cache instances in the system and incapsulating
          operations on all of them.
        * Introduced Table_cache_iterator, an iterator over all
          used TABLE instances for the table.
     @ sql/sql_class.h
        Added new status variables for table cache - Table_open_cache_hits,
        Table_open_cache_misses, Table_open_cache_overflows.
     @ sql/sql_handler.cc
        close_thread_table() no longer has any return value.
     @ sql/sql_parse.cc
        cached_open_tables() function was superceded by
        Table_cache_manager::cached_tables() method.
     @ sql/sql_partition.cc
        Do not set TABLE_SHARE::version without holding LOCK_open mutex and
        locks on all table cache instances. close_all_tables_for_name() will
        do it anyway (and in the correct fashion).
     @ sql/sql_table.cc
        intern_close_table() now also does my_free() of the TABLE object.
     @ sql/sql_test.cc
        Moved most of contents of print_cached_tables() method to
        Table_cache_manager/Table_cache::print_tables() methods.
        To support this change made lock_descriptions[] array visible
        outside of sql_test.cc file. Changed code to use
        Table_cache_manager::cached_tables() method instead of
        cached_open_tables() function.
     @ sql/sql_test.h
        Made lock_descriptions[] array visible outside of sql_test.cc file.
     @ sql/sys_vars.cc
        Introduced new global, non-dynamic variable/start-up parameter for
        number of table cache instances in server -
        table_open_cache_instances.
        Adjusted code handling existing table_open_cache parameter to take
        into account that now it limits total number of TABLE objects in
        all table cache instances in the server.
     @ sql/table.cc
        Added allocation of TABLE_SHARE::cache_element[] array, which
        allows quick access to Table_cache_element objects corresponding
        to this table in each of the table cache instances, to
        alloc_table_share().
        Changed TABLE_SHARE::visit_subgraph() method:
        * to acquire not only LOCK_open but also locks on all table cache
          instances, since now information about used TABLE objects is
          protected by the latter
        * use new Table_cache_iterator for iterating over all used TABLE
          objects in all table cache instances
     @ sql/table.h
        TABLE_SHARE no longer contains lists of all used and unused TABLE
        objects for this table. These lists are now local to table cache
        instance and reside in corresponding Table_cache_element object
        in each table cache.
        Consequently TABLE::share_prev/share_next pointers were renamed
        to TABLE::cache_prev/cache_next.
        Added TABLE_SHARE::cache_element[] array for quick access to
        Table_cache_element objects corresponding to this table in each
        of table cache instances.

    added:
      mysql-test/suite/sys_vars/r/table_open_cache_instances_basic.result
      mysql-test/suite/sys_vars/t/table_open_cache_instances_basic.test
    modified:
      mysql-test/r/mysqld--help-notwin.result
      mysql-test/r/mysqld--help-win.result
      mysql-test/r/status.result
      mysql-test/suite/perfschema/r/func_mutex.result
      mysql-test/suite/perfschema/t/func_mutex.test
      mysql-test/t/status.test
      sql/mysqld.cc
      sql/mysqld.h
      sql/sql_base.cc
      sql/sql_base.h
      sql/sql_class.h
      sql/sql_handler.cc
      sql/sql_parse.cc
      sql/sql_partition.cc
      sql/sql_table.cc
      sql/sql_test.cc
      sql/sql_test.h
      sql/sys_vars.cc
      sql/table.cc
      sql/table.h
 3898 Mayank Prasad	2012-05-21
      BUG#13784804 : REMOVE THE LEGACY SHA1 AND MD5 CODE FROM THE SERVER
      
      Details:
       - Removing MySQL implementation of MD5 and SHA1.
       - Removed -DWITH_SSL=no compile option for SSL. 
      
      NOTE:
        As per include/my_global.h, HAVE_OPENSSL is undefined in embedded library:
       
         #ifdef EMBEDDED_LIBRARY
       
         /* Things we don't need in the embedded version of MySQL */
         /* TODO HF add #undef HAVE_VIO if we don't want client in embedded library
         */
       
         #undef HAVE_OPENSSL
         #undef HAVE_SMEM                /* No shared memory */
       
         #endif /* EMBEDDED_LIBRARY */
       
        If embedded library is built with -DWITH_SSL=system option, earlier (before
        this bug) it was suppose to go into MySQL implementation of SSL but now this
        has been removed, so it wouldn't have SSL support.

    removed:
      mysys/md5.c
      mysys/sha1.c
    modified:
      CMakeLists.txt
      cmake/ssl.cmake
      include/my_md5.h
      include/sha1.h
      mysql-test/t/disabled.def
      mysys/CMakeLists.txt
      sql/md5.cc
      sql/sha1.cc
=== modified file 'mysql-test/r/mysqld--help-notwin.result'
--- a/mysql-test/r/mysqld--help-notwin.result	2012-05-09 16:53:03 +0000
+++ b/mysql-test/r/mysqld--help-notwin.result	2012-05-21 17:56:02 +0000
@@ -865,7 +865,10 @@ The following options may be given as th
  --table-definition-cache=# 
  The number of cached table definitions
  --table-open-cache=# 
- The number of cached open tables
+ The number of cached open tables (total for all table
+ cache instances)
+ --table-open-cache-instances=# 
+ The number of table cache instances
  --tc-heuristic-recover=name 
  Decision to use in heuristic recover process. Possible
  values are COMMIT or ROLLBACK.
@@ -1170,6 +1173,7 @@ sysdate-is-now FALSE
 table-cache 400
 table-definition-cache 400
 table-open-cache 400
+table-open-cache-instances 1
 tc-heuristic-recover COMMIT
 thread-cache-size 0
 thread-handling one-thread-per-connection

=== modified file 'mysql-test/r/mysqld--help-win.result'
--- a/mysql-test/r/mysqld--help-win.result	2012-05-09 16:53:03 +0000
+++ b/mysql-test/r/mysqld--help-win.result	2012-05-21 17:56:02 +0000
@@ -873,7 +873,10 @@ The following options may be given as th
  --table-definition-cache=# 
  The number of cached table definitions
  --table-open-cache=# 
- The number of cached open tables
+ The number of cached open tables (total for all table
+ cache instances)
+ --table-open-cache-instances=# 
+ The number of table cache instances
  --tc-heuristic-recover=name 
  Decision to use in heuristic recover process. Possible
  values are COMMIT or ROLLBACK.
@@ -1181,6 +1184,7 @@ sysdate-is-now FALSE
 table-cache 400
 table-definition-cache 400
 table-open-cache 400
+table-open-cache-instances 1
 tc-heuristic-recover COMMIT
 thread-cache-size 0
 thread-handling one-thread-per-connection

=== modified file 'mysql-test/r/status.result'
--- a/mysql-test/r/status.result	2012-03-06 14:29:42 +0000
+++ b/mysql-test/r/status.result	2012-05-21 17:56:02 +0000
@@ -238,5 +238,104 @@ SELECT 9;
 9
 DROP PROCEDURE p1;
 DROP FUNCTION f1;
+#
+# Test coverage for status variables which were introduced by
+# WL#5772 "Add partitioned Table Definition Cache to avoid
+# using LOCK_open and its derivatives in DML queries".
+#
+create table t1 (i int);
+create table t2 (j int);
+create table t3 (k int);
+# Flush table cache to ensure that it is empty and reset status
+# variables. Since to test cache overflow we will need to reduce
+# its size, also save original table cache size.
+flush tables;
+flush status;
+set @old_table_open_cache= @@table_open_cache;
+# Check that after reset all status variables are zero.
+show status like 'table_open_cache_%';
+Variable_name	Value
+Table_open_cache_hits	0
+Table_open_cache_misses	0
+Table_open_cache_overflows	0
+# The first statement accessing t1 after flush should result
+# in table cache miss.
+select * from t1;
+i
+show status like 'table_open_cache_%';
+Variable_name	Value
+Table_open_cache_hits	0
+Table_open_cache_misses	1
+Table_open_cache_overflows	0
+# The second statement accessing the same table should
+# result in table cache hit.
+select * from t1;
+i
+show status like 'table_open_cache_%';
+Variable_name	Value
+Table_open_cache_hits	1
+Table_open_cache_misses	1
+Table_open_cache_overflows	0
+# Again table cache miss if accessing different table.
+select * from t2;
+j
+show status like 'table_open_cache_%';
+Variable_name	Value
+Table_open_cache_hits	1
+Table_open_cache_misses	2
+Table_open_cache_overflows	0
+# And cache hit then accessing it second time.
+select * from t2;
+j
+show status like 'table_open_cache_%';
+Variable_name	Value
+Table_open_cache_hits	2
+Table_open_cache_misses	2
+Table_open_cache_overflows	0
+# The below statement should result in 2 cache hits and 
+# 4 cache misses since it needs 6 table instances in total.
+select * from t1 as a, t2 as b, t1 as c, t2 as d, t1 as e, t2 as f;
+i	j	i	j	i	j
+show status like 'table_open_cache_%';
+Variable_name	Value
+Table_open_cache_hits	4
+Table_open_cache_misses	6
+Table_open_cache_overflows	0
+# Reduce size of table cache to check that status
+# variable tracking cache overflows works.
+set @@global.table_open_cache= 4;
+# The below statement should result in table cache hit, but
+# as a side effect it should result in trimming of table
+# cache by 2 TABLE instances, meaning that overflow counter
+# will get increased by 2.
+select * from t1;
+i
+show status like 'table_open_cache_%';
+Variable_name	Value
+Table_open_cache_hits	5
+Table_open_cache_misses	6
+Table_open_cache_overflows	2
+# This statement should result in 4 cache hits, 2 cache misses/
+# overflows.
+select * from t1 as a, t2 as b, t1 as c, t2 as d, t1 as e, t2 as f;
+i	j	i	j	i	j
+show status like 'table_open_cache_%';
+Variable_name	Value
+Table_open_cache_hits	9
+Table_open_cache_misses	8
+Table_open_cache_overflows	4
+# Finally, the below statement should result in 1 cache miss
+# and 1 overflow since it accesses table which is not yet in
+# cache and table cache is full.
+select * from t3;
+k
+show status like 'table_open_cache_%';
+Variable_name	Value
+Table_open_cache_hits	9
+Table_open_cache_misses	9
+Table_open_cache_overflows	5
+# Cleanup
+set @@global.table_open_cache= @old_table_open_cache;
+drop tables t1, t2, t3;
 set @@global.concurrent_insert= @old_concurrent_insert;
 SET GLOBAL log_output = @old_log_output;

=== modified file 'mysql-test/suite/perfschema/r/func_mutex.result'
--- a/mysql-test/suite/perfschema/r/func_mutex.result	2010-11-18 16:34:56 +0000
+++ b/mysql-test/suite/perfschema/r/func_mutex.result	2012-05-21 17:56:02 +0000
@@ -14,7 +14,7 @@ id	b
 1	initial value
 SET @before_count = (SELECT SUM(TIMER_WAIT)
 FROM performance_schema.events_waits_history_long
-WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_open'));
+WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_table_cache'));
 SELECT * FROM t1;
 id	b
 1	initial value
@@ -27,12 +27,12 @@ id	b
 8	initial value
 SET @after_count = (SELECT SUM(TIMER_WAIT)
 FROM performance_schema.events_waits_history_long
-WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_open'));
+WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_table_cache'));
 SELECT IF((@after_count - @before_count) > 0, 'Success', 'Failure') test_fm1_timed;
 test_fm1_timed
 Success
 UPDATE performance_schema.setup_instruments SET enabled = 'NO'
-WHERE NAME = 'wait/synch/mutex/sql/LOCK_open';
+WHERE NAME = 'wait/synch/mutex/sql/LOCK_table_cache';
 TRUNCATE TABLE performance_schema.events_waits_history_long;
 TRUNCATE TABLE performance_schema.events_waits_history;
 TRUNCATE TABLE performance_schema.events_waits_current;
@@ -41,7 +41,7 @@ id	b
 1	initial value
 SET @before_count = (SELECT SUM(TIMER_WAIT)
 FROM performance_schema.events_waits_history_long
-WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_open'));
+WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_table_cache'));
 SELECT * FROM t1;
 id	b
 1	initial value
@@ -54,7 +54,7 @@ id	b
 8	initial value
 SET @after_count = (SELECT SUM(TIMER_WAIT)
 FROM performance_schema.events_waits_history_long
-WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_open'));
+WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_table_cache'));
 SELECT IF((COALESCE(@after_count, 0) - COALESCE(@before_count, 0)) = 0, 'Success', 'Failure') test_fm2_timed;
 test_fm2_timed
 Success

=== modified file 'mysql-test/suite/perfschema/t/func_mutex.test'
--- a/mysql-test/suite/perfschema/t/func_mutex.test	2011-10-19 21:49:22 +0000
+++ b/mysql-test/suite/perfschema/t/func_mutex.test	2012-05-21 17:56:02 +0000
@@ -39,18 +39,18 @@ SELECT * FROM t1 WHERE id = 1;
 
 SET @before_count = (SELECT SUM(TIMER_WAIT)
                      FROM performance_schema.events_waits_history_long
-                     WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_open'));
+                     WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_table_cache'));
 
 SELECT * FROM t1;
 
 SET @after_count = (SELECT SUM(TIMER_WAIT)
                     FROM performance_schema.events_waits_history_long
-                    WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_open'));
+                    WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_table_cache'));
 
 SELECT IF((@after_count - @before_count) > 0, 'Success', 'Failure') test_fm1_timed;
 
 UPDATE performance_schema.setup_instruments SET enabled = 'NO'
-WHERE NAME = 'wait/synch/mutex/sql/LOCK_open';
+WHERE NAME = 'wait/synch/mutex/sql/LOCK_table_cache';
 
 TRUNCATE TABLE performance_schema.events_waits_history_long;
 TRUNCATE TABLE performance_schema.events_waits_history;
@@ -60,13 +60,13 @@ SELECT * FROM t1 WHERE id = 1;
 
 SET @before_count = (SELECT SUM(TIMER_WAIT)
                      FROM performance_schema.events_waits_history_long
-                     WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_open'));
+                     WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_table_cache'));
 
 SELECT * FROM t1;
 
 SET @after_count = (SELECT SUM(TIMER_WAIT)
                     FROM performance_schema.events_waits_history_long
-                    WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_open'));
+                    WHERE (EVENT_NAME = 'wait/synch/mutex/sql/LOCK_table_cache'));
 
 SELECT IF((COALESCE(@after_count, 0) - COALESCE(@before_count, 0)) = 0, 'Success', 'Failure') test_fm2_timed;
 

=== added file 'mysql-test/suite/sys_vars/r/table_open_cache_instances_basic.result'
--- a/mysql-test/suite/sys_vars/r/table_open_cache_instances_basic.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/sys_vars/r/table_open_cache_instances_basic.result	2012-05-21 17:56:02 +0000
@@ -0,0 +1,51 @@
+####################################################################
+#   Displaying default value                                       #
+####################################################################
+SELECT @@GLOBAL.table_open_cache_instances;
+@@GLOBAL.table_open_cache_instances
+1
+####################################################################
+# Check that value cannot be set (this variable is settable only   #
+# at start-up).                                                    #
+####################################################################
+SET @@GLOBAL.table_open_cache_instances=1;
+ERROR HY000: Variable 'table_open_cache_instances' is a read only variable
+SELECT @@GLOBAL.table_open_cache_instances;
+@@GLOBAL.table_open_cache_instances
+1
+#################################################################
+# Check if the value in GLOBAL Table matches value in variable  #
+#################################################################
+SELECT @@GLOBAL.table_open_cache_instances = VARIABLE_VALUE
+FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES
+WHERE VARIABLE_NAME='table_open_cache_instances';
+@@GLOBAL.table_open_cache_instances = VARIABLE_VALUE
+1
+SELECT @@GLOBAL.table_open_cache_instances;
+@@GLOBAL.table_open_cache_instances
+1
+SELECT VARIABLE_VALUE
+FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES 
+WHERE VARIABLE_NAME='table_open_cache_instances';
+VARIABLE_VALUE
+1
+######################################################################
+#  Check if accessing variable with and without GLOBAL point to same #
+#  variable                                                          #
+######################################################################
+SELECT @@table_open_cache_instances = @@GLOBAL.table_open_cache_instances;
+@@table_open_cache_instances = @@GLOBAL.table_open_cache_instances
+1
+######################################################################
+#  Check if variable has only the GLOBAL scope                       #
+######################################################################
+SELECT @@table_open_cache_instances;
+@@table_open_cache_instances
+1
+SELECT @@GLOBAL.table_open_cache_instances;
+@@GLOBAL.table_open_cache_instances
+1
+SELECT @@local.table_open_cache_instances;
+ERROR HY000: Variable 'table_open_cache_instances' is a GLOBAL variable
+SELECT @@SESSION.table_open_cache_instances;
+ERROR HY000: Variable 'table_open_cache_instances' is a GLOBAL variable

=== added file 'mysql-test/suite/sys_vars/t/table_open_cache_instances_basic.test'
--- a/mysql-test/suite/sys_vars/t/table_open_cache_instances_basic.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/suite/sys_vars/t/table_open_cache_instances_basic.test	2012-05-21 17:56:02 +0000
@@ -0,0 +1,73 @@
+######### mysql-test\t\table_open_cache_instances_basic.test ##################
+#                                                                             #
+# Variable Name: table_open_cache_instances                                   #
+# Scope: Global                                                               #
+# Access Type: Static                                                         #
+# Data Type: Integer                                                          #
+#                                                                             #
+#                                                                             #
+# Creation Date: 2012-04-25                                                   #
+# Author : Mikael Ronstrom                                                    #
+#                                                                             #
+#                                                                             #
+#                                                                             #
+# Description:                                                                #
+# Test case for static system variable table_open_cache_instances,            #
+# Checks the behavior of this variable in the following ways:                 #
+#  * Value Check                                                              #
+#  * Scope Check                                                              #
+#                                                                             #
+#                                                                             #
+###############################################################################
+
+
+--echo ####################################################################
+--echo #   Displaying default value                                       #
+--echo ####################################################################
+SELECT @@GLOBAL.table_open_cache_instances;
+
+
+--echo ####################################################################
+--echo # Check that value cannot be set (this variable is settable only   #
+--echo # at start-up).                                                    #
+--echo ####################################################################
+--error ER_INCORRECT_GLOBAL_LOCAL_VAR
+SET @@GLOBAL.table_open_cache_instances=1;
+
+SELECT @@GLOBAL.table_open_cache_instances;
+
+
+--echo #################################################################
+--echo # Check if the value in GLOBAL Table matches value in variable  #
+--echo #################################################################
+SELECT @@GLOBAL.table_open_cache_instances = VARIABLE_VALUE
+FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES
+WHERE VARIABLE_NAME='table_open_cache_instances';
+
+SELECT @@GLOBAL.table_open_cache_instances;
+
+SELECT VARIABLE_VALUE
+FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES 
+WHERE VARIABLE_NAME='table_open_cache_instances';
+
+
+--echo ######################################################################
+--echo #  Check if accessing variable with and without GLOBAL point to same #
+--echo #  variable                                                          #
+--echo ######################################################################
+SELECT @@table_open_cache_instances = @@GLOBAL.table_open_cache_instances;
+
+
+--echo ######################################################################
+--echo #  Check if variable has only the GLOBAL scope                       #
+--echo ######################################################################
+
+SELECT @@table_open_cache_instances;
+
+SELECT @@GLOBAL.table_open_cache_instances;
+
+--error ER_INCORRECT_GLOBAL_LOCAL_VAR
+SELECT @@local.table_open_cache_instances;
+
+--error ER_INCORRECT_GLOBAL_LOCAL_VAR
+SELECT @@SESSION.table_open_cache_instances;

=== modified file 'mysql-test/t/status.test'
--- a/mysql-test/t/status.test	2011-09-07 10:08:09 +0000
+++ b/mysql-test/t/status.test	2012-05-21 17:56:02 +0000
@@ -354,6 +354,75 @@ DROP FUNCTION f1;
 
 # End of 5.1 tests
 
+
+--echo #
+--echo # Test coverage for status variables which were introduced by
+--echo # WL#5772 "Add partitioned Table Definition Cache to avoid
+--echo # using LOCK_open and its derivatives in DML queries".
+--echo #
+create table t1 (i int);
+create table t2 (j int);
+create table t3 (k int);
+--echo # Flush table cache to ensure that it is empty and reset status
+--echo # variables. Since to test cache overflow we will need to reduce
+--echo # its size, also save original table cache size.
+flush tables;
+flush status;
+set @old_table_open_cache= @@table_open_cache;
+
+--echo # Check that after reset all status variables are zero.
+show status like 'table_open_cache_%';
+
+--echo # The first statement accessing t1 after flush should result
+--echo # in table cache miss.
+select * from t1;
+show status like 'table_open_cache_%';
+
+--echo # The second statement accessing the same table should
+--echo # result in table cache hit.
+select * from t1;
+show status like 'table_open_cache_%';
+
+--echo # Again table cache miss if accessing different table.
+select * from t2;
+show status like 'table_open_cache_%';
+
+--echo # And cache hit then accessing it second time.
+select * from t2;
+show status like 'table_open_cache_%';
+
+--echo # The below statement should result in 2 cache hits and 
+--echo # 4 cache misses since it needs 6 table instances in total.
+select * from t1 as a, t2 as b, t1 as c, t2 as d, t1 as e, t2 as f;
+show status like 'table_open_cache_%';
+
+--echo # Reduce size of table cache to check that status
+--echo # variable tracking cache overflows works.
+set @@global.table_open_cache= 4;
+
+--echo # The below statement should result in table cache hit, but
+--echo # as a side effect it should result in trimming of table
+--echo # cache by 2 TABLE instances, meaning that overflow counter
+--echo # will get increased by 2.
+select * from t1;
+show status like 'table_open_cache_%';
+
+--echo # This statement should result in 4 cache hits, 2 cache misses/
+--echo # overflows.
+select * from t1 as a, t2 as b, t1 as c, t2 as d, t1 as e, t2 as f;
+show status like 'table_open_cache_%';
+
+--echo # Finally, the below statement should result in 1 cache miss
+--echo # and 1 overflow since it accesses table which is not yet in
+--echo # cache and table cache is full.
+select * from t3;
+show status like 'table_open_cache_%';
+
+--echo # Cleanup
+set @@global.table_open_cache= @old_table_open_cache;
+drop tables t1, t2, t3;
+
+
 # Restore global concurrent_insert value. Keep in the end of the test file.
 --connection default
 set @@global.concurrent_insert= @old_concurrent_insert;

=== modified file 'sql/mysqld.cc'
--- a/sql/mysqld.cc	2012-05-18 15:21:06 +0000
+++ b/sql/mysqld.cc	2012-05-21 17:56:02 +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
@@ -493,6 +493,8 @@ int32 thread_running;
 ulong thread_created;
 ulong back_log, connect_timeout, concurrency, server_id;
 ulong table_cache_size, table_def_size;
+ulong table_cache_instances;
+ulong table_cache_size_per_instance;
 ulong what_to_log;
 ulong slow_launch_time;
 int32 slave_open_temp_tables;
@@ -3666,6 +3668,7 @@ int init_common_variables()
     }
     open_files_limit= files;
   }
+  table_cache_size_per_instance= table_cache_size / table_cache_instances;
   unireg_init(opt_specialflag); /* Set up extern variabels */
   if (!(my_default_lc_messages=
         my_locale_by_name(lc_messages)))
@@ -6957,7 +6960,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_manager.cached_tables();
   return 0;
 }
 
@@ -7475,6 +7478,9 @@ SHOW_VAR status_vars[]= {
 #endif /* HAVE_OPENSSL */
   {"Table_locks_immediate",    (char*) &locks_immediate,        SHOW_LONG},
   {"Table_locks_waited",       (char*) &locks_waited,           SHOW_LONG},
+  {"Table_open_cache_hits",    (char*) offsetof(STATUS_VAR, table_open_cache_hits), SHOW_LONGLONG_STATUS},
+  {"Table_open_cache_misses",  (char*) offsetof(STATUS_VAR, table_open_cache_misses), SHOW_LONGLONG_STATUS},
+  {"Table_open_cache_overflows",(char*) offsetof(STATUS_VAR, table_open_cache_overflows), SHOW_LONGLONG_STATUS},
 #ifdef HAVE_MMAP
   {"Tc_log_max_pages_used",    (char*) &tc_log_max_pages_used,  SHOW_LONG},
   {"Tc_log_page_size",         (char*) &tc_log_page_size,       SHOW_LONG},

=== modified file 'sql/mysqld.h'
--- a/sql/mysqld.h	2012-05-04 10:00:43 +0000
+++ b/sql/mysqld.h	2012-05-21 17:56:02 +0000
@@ -184,6 +184,7 @@ extern int32 slave_open_temp_tables;
 extern ulong query_cache_size, query_cache_min_res_unit;
 extern ulong slow_launch_threads, slow_launch_time;
 extern ulong table_cache_size, table_def_size;
+extern ulong table_cache_size_per_instance, table_cache_instances;
 extern MYSQL_PLUGIN_IMPORT ulong max_connections;
 extern ulong max_connect_errors, connect_timeout;
 extern my_bool opt_slave_allow_batching;

=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc	2012-05-17 12:55:26 +0000
+++ b/sql/sql_base.cc	2012-05-21 17:56:02 +0000
@@ -57,6 +57,7 @@
 #ifdef  __WIN__
 #include <io.h>
 #endif
+#include "sql_test.h" // lock_descriptions[]
 
 
 bool
@@ -158,15 +159,45 @@ Repair_mrg_table_error_handler::handle_c
 */
 
 /**
-  Protects table_def_hash, used and unused lists in the
-  TABLE_SHARE object, LRU lists of used TABLEs and used
-  TABLE_SHAREs, refresh_version and the table id counter.
+  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.
+  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.
+  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.
+  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.
+  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 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.
+  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.
+  9) share->m_flush_tickets
 */
 mysql_mutex_t LOCK_open;
 
 #ifdef HAVE_PSI_INTERFACE
 static PSI_mutex_key key_LOCK_open;
+static PSI_mutex_key key_LOCK_table_cache;
 static PSI_mutex_info all_tdc_mutexes[]= {
+  { &key_LOCK_table_cache, "LOCK_table_cache", 0},
   { &key_LOCK_open, "LOCK_open", PSI_FLAG_GLOBAL }
 };
 
@@ -199,69 +230,344 @@ static void modify_slave_open_temp_table
   }
 }
 
+
+/**
+  Container for all table cache instances in the system.
+*/
+Table_cache_manager table_cache_manager;
+
+
+/**
+  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).
+  */
+  typedef I_P_List <TABLE,
+                    I_P_List_adapter<TABLE,
+                                     &TABLE::cache_next,
+                                     &TABLE::cache_prev> > TABLE_list;
+
+  TABLE_list used_tables;
+  TABLE_list free_tables;
+  TABLE_SHARE *share;
+
+  Table_cache_element(TABLE_SHARE *share_arg)
+    : share(share_arg)
+  {
+  }
+};
+
+
+extern "C" uchar *table_cache_key(const uchar *record,
+                                  size_t *length,
+                                  my_bool not_used __attribute__((unused)))
+{
+  Table_cache_element *entry= (Table_cache_element*) record;
+  *length= entry->share->table_cache_key.length;
+  return (uchar*) entry->share->table_cache_key.str;
+}
+
+
+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()
+{
+  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;
+  }
+  return false;
+}
+
+
+/** Destroy instance of table cache. */
+
+void Table_cache::destroy()
+{
+  my_hash_free(&m_cache);
+  mysql_mutex_destroy(&m_lock);
+}
+
+
+/**
+  Initialize all instances of table cache to be used by server.
+
+  @retval false - success.
+  @retval true  - failure.
+*/
+
+bool Table_cache_manager::init()
+{
+  for (uint i= 0; i < table_cache_instances; i++)
+  {
+    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_manager::destroy()
+{
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].destroy();
+}
+
+
 /**
-   Total number of TABLE instances for tables in the table definition cache
-   (both in use by threads and not in use). This value is accessible to user
-   as "Open_tables" status variable.
-*/
-uint  table_cache_count= 0;
-/**
-   List that contains all TABLE instances for tables in the table definition
-   cache that are not in use by any thread. Recently used TABLE instances are
-   appended to the end of the list. Thus the beginning of the list contains
-   tables which have been least recently used.
+  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.
 */
-TABLE *unused_tables;
+
+uint Table_cache_manager::cached_tables()
+{
+  uint result= 0;
+
+  for (uint i= 0; i < table_cache_instances; i++)
+    result+= m_table_cache[i].cached_tables();
+
+  return result;
+}
+
+
+/**
+  Acquire locks on all instances of table cache and table definition
+  cache (i.e. LOCK_open).
+*/
+
+void Table_cache_manager::lock_all_and_tdc()
+{
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].lock();
+
+  mysql_mutex_lock(&LOCK_open);
+}
+
+
+/**
+  Release locks on all instances of table cache and table definition
+  cache.
+*/
+
+void Table_cache_manager::unlock_all_and_tdc()
+{
+  mysql_mutex_unlock(&LOCK_open);
+
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].unlock();
+}
+
+
+/**
+  Assert that caller owns locks on all instances of table cache.
+*/
+
+void Table_cache_manager::assert_owner_all()
+{
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].assert_owner();
+}
+
+
+/**
+  Assert that caller owns locks on all instances of table cache
+  and table definition cache.
+*/
+
+void Table_cache_manager::assert_owner_all_and_tdc()
+{
+  assert_owner_all();
+
+  mysql_mutex_assert_owner(&LOCK_open);
+}
+
+
+/**
+  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_manager.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)
+  {
+    Table_cache_element *el;
+
+    if ((el= share->cache_element[current_cache_index]))
+    {
+      if ((current_table= el->used_tables.front()))
+        break;
+    }
+  }
+}
+
+
+/**
+  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)
+{
+  table_cache_manager.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 Table_cache_iterator::rewind()
+{
+  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;
-static bool table_def_shutdown_in_progress= 0;
+static bool table_def_inited= false;
+static bool table_def_shutdown_in_progress= false;
 
 static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables,
                                            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 *entry);
 static bool
 has_write_table_with_auto_increment(TABLE_LIST *tables);
 static bool
 has_write_table_with_auto_increment_and_select(TABLE_LIST *tables);
 static bool has_write_table_auto_increment_not_first_in_pk(TABLE_LIST *tables);
 
-uint cached_open_tables(void)
+
+/**
+  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)
 {
-  return table_cache_count;
+  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();
+}
+
+
+/** 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= NULL;
+  }
+  check_unused();
 }
 
 
 #ifdef EXTRA_DEBUG
-static void check_unused(void)
+void Table_cache::check_unused()
 {
-  uint count= 0, open_files= 0, idx= 0;
-  TABLE *cur_link, *start_link, *entry;
-  TABLE_SHARE *share;
+  uint count= 0;
 
-  if ((start_link=cur_link=unused_tables))
+  if (m_unused_tables != NULL)
   {
+    TABLE *cur_link= m_unused_tables;
+    TABLE *start_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")); /* purecov: inspected */
-	return; /* purecov: inspected */
+	DBUG_PRINT("error",("Unused_links aren't linked properly"));
+	return;
       }
-    } while (count++ < table_cache_count &&
-	     (cur_link=cur_link->next) != start_link);
+    } 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"));
   }
-  for (idx=0 ; idx < table_def_cache.records ; idx++)
+
+  for (uint idx= 0; idx < m_cache.records; idx++)
   {
-    share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx);
+    Table_cache_element *el=
+      (Table_cache_element*) my_hash_element(&m_cache, idx);
 
-    TABLE_SHARE::TABLE_list::Iterator it(share->free_tables);
+    Table_cache_element::TABLE_list::Iterator it(el->free_tables);
+    TABLE *entry;
     while ((entry= it++))
     {
       /* We must not have TABLEs in the free list that have their file closed. */
@@ -270,35 +576,100 @@ static void check_unused(void)
       DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
 
       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"));
       count--;
-      open_files++;
     }
-    it.init(share->used_tables);
+    it.init(el->used_tables);
     while ((entry= it++))
     {
       if (!entry->in_use)
-      {
-        DBUG_PRINT("error",("Unused table is in share's list of used tables")); /* purecov: inspected */
-      }
-      open_files++;
+        DBUG_PRINT("error",("Unused table is in share's list of used tables"));
     }
   }
+
   if (count != 0)
+    DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d",
+                        count));
+}
+#endif
+
+
+#ifndef DBUG_OFF
+
+/**
+  Print debug information for the contents of all table cache instances.
+*/
+
+void Table_cache_manager::print_tables()
+{
+  puts("DB             Table                            Version  Thread  Open  Lock");
+
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].print_tables();
+}
+
+
+/**
+  Print debug information for the contents of the table cache.
+*/
+
+void Table_cache::print_tables()
+{
+  uint unused= 0;
+  uint count=0;
+
+  compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions));
+
+  for (uint idx= 0; idx < m_cache.records; idx++)
   {
-    DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */
-			count)); /* purecov: inspected */
+    Table_cache_element *el=
+      (Table_cache_element*) my_hash_element(&m_cache, idx);
+
+    Table_cache_element::TABLE_list::Iterator it(el->used_tables);
+    TABLE *entry;
+    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");
+    }
+  }
+
+  if (m_unused_tables != NULL)
+  {
+    TABLE *start_link= m_unused_tables;
+    TABLE *lnk= m_unused_tables;
+    do
+    {
+      if (lnk != lnk->next->prev || lnk != lnk->prev->next)
+      {
+	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);
 }
-#else
-#define check_unused()
+
 #endif
 
 
 /**
-  Create a table cache key
+  Create a table cache/table definition cache key
 
   @param thd        Thread context
   @param key        Buffer for the key to be created (must be of
@@ -403,7 +774,6 @@ static void table_def_free_entry(TABLE_S
 
 bool table_def_init(void)
 {
-  table_def_inited= 1;
 #ifdef HAVE_PSI_INTERFACE
   init_tdc_psi_keys();
 #endif
@@ -411,6 +781,17 @@ bool table_def_init(void)
   oldest_unused_share= &end_of_unused_share;
   end_of_unused_share.prev= &oldest_unused_share;
 
+  if (table_cache_manager.init())
+  {
+    mysql_mutex_destroy(&LOCK_open);
+    return true;
+  }
+
+  /*
+    It is safe to destroy zero-initialized HASH even if its
+    initialization has failed.
+  */
+  table_def_inited= true;
 
   return my_hash_init(&table_def_cache, &my_charset_bin, table_def_size,
                       0, 0, table_def_key,
@@ -428,15 +809,15 @@ void table_def_start_shutdown(void)
 {
   if (table_def_inited)
   {
-    mysql_mutex_lock(&LOCK_open);
+    table_cache_manager.lock_all_and_tdc();
     /*
       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.
     */
-    table_def_shutdown_in_progress= TRUE;
-    mysql_mutex_unlock(&LOCK_open);
+    table_def_shutdown_in_progress= true;
+    table_cache_manager.unlock_all_and_tdc();
     /* Free all cached but unused TABLEs and TABLE_SHAREs. */
     close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT);
   }
@@ -448,9 +829,10 @@ void table_def_free(void)
   DBUG_ENTER("table_def_free");
   if (table_def_inited)
   {
-    table_def_inited= 0;
+    table_def_inited= false;
     /* Free table definitions. */
     my_hash_free(&table_def_cache);
+    table_cache_manager.destroy();
     mysql_mutex_destroy(&LOCK_open);
   }
   DBUG_VOID_RETURN;
@@ -463,120 +845,249 @@ 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.
+
+    Note that we might need to free more than one TABLE object, and thus
+    need the below loop, in case when table_cache_size is changed dynamically,
+    at server run time.
+  */
+  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_def_add_used_table(THD *thd, TABLE *table)
+bool Table_cache::add_used_table(THD *thd, TABLE *table)
 {
+  Table_cache_element *el;
+
+  assert_owner();
+
   DBUG_ASSERT(table->in_use == thd);
-  table->s->used_tables.push_front(table);
-  table_cache_count++;
+
+  /*
+    Try to get Table_cache_element representing this table in the cache
+    from array in the TABLE_SHARE.
+  */
+  el= table->s->cache_element[table_cache_manager.cache_index(this)];
+
+  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[table_cache_manager.cache_index(this)]= el;
+  }
+
+  /* Add table to the used tables list */
+  el->used_tables.push_front(table);
+
+  m_table_count++;
+
+  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_def_remove_table(TABLE *table)
+void Table_cache::remove_table(TABLE *table)
 {
+  Table_cache_element *el=
+    table->s->cache_element[table_cache_manager.cache_index(this)];
+
+  assert_owner();
+
   if (table->in_use)
   {
-    /* Remove from per-share chain of used TABLE objects. */
-    table->s->used_tables.remove(table);
+    /* Remove from per-table chain of used TABLE objects. */
+    el->used_tables.remove(table);
   }
   else
   {
-    /* Remove from per-share chain of unused TABLE objects. */
-    table->s->free_tables.remove(table);
+    /* Remove from per-table chain of unused TABLE objects. */
+    el->free_tables.remove(table);
 
-    /* And global unused chain. */
-    table->next->prev=table->prev;
-    table->prev->next=table->next;
-    if (table == unused_tables)
-    {
-      unused_tables=unused_tables->next;
-      if (table == unused_tables)
-	unused_tables=0;
-    }
-    check_unused();
+    /* And per-cache unused chain. */
+    unlink_unused_table(table);
+  }
+
+  m_table_count--;
+
+  if (el->used_tables.is_empty() && el->free_tables.is_empty())
+  {
+    (void) my_hash_delete(&m_cache, (uchar*) el);
+    /*
+      Remove reference to deleted cache element from array
+      in the TABLE_SHARE.
+    */
+    table->s->cache_element[table_cache_manager.cache_index(this)]= NULL;
   }
-  table_cache_count--;
 }
 
 
 /**
-   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_def_use_table(THD *thd, 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;
+
+  assert_owner();
+
+  *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 tables for this share. */
-  table->s->free_tables.remove(table);
-  /* Unlink able from global unused tables list. */
-  if (table == unused_tables)
-  {						// First unused
-    unused_tables=unused_tables->next;	        // Remove from link
-    if (table == unused_tables)
-      unused_tables=0;
+    /*
+      Unlink table from list of unused TABLE objects for this
+      table in this cache.
+    */
+    el->free_tables.remove(table);
+
+    /* 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();
-  /* Add table to list of used tables for this share. */
-  table->s->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_def_unuse_table(TABLE *table)
+void Table_cache::release_table(THD *thd, TABLE *table)
 {
+  Table_cache_element *el=
+    table->s->cache_element[table_cache_manager.cache_index(this)];
+
+  assert_owner();
+
   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 share. */
-  table->s->used_tables.remove(table);
-  /* Add table to the list of unused TABLE objects for this share. */
-  table->s->free_tables.push_front(table);
-  /* Also link it last in the global list of unused TABLE objects. */
-  if (unused_tables)
-  {
-    table->next=unused_tables;
-    table->prev=unused_tables->prev;
-    unused_tables->prev=table;
-    table->prev->next=table;
-  }
-  else
-    unused_tables=table->next=table->prev=table;
-  check_unused();
+  /* 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 objects for the table in this cache. */
+  el->free_tables.push_front(table);
+  /* 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);
 }
 
 
@@ -906,11 +1417,12 @@ OPEN_TABLE_LIST *list_open_tables(THD *t
   TABLE_LIST table_list;
   DBUG_ENTER("list_open_tables");
 
-  mysql_mutex_lock(&LOCK_open);
   memset(&table_list, 0, sizeof(table_list));
   start_list= &open_list;
   open_list=0;
 
+  table_cache_manager.lock_all_and_tdc();
+
   for (uint idx=0 ; result == 0 && idx < table_def_cache.records; idx++)
   {
     TABLE_SHARE *share= (TABLE_SHARE *)my_hash_element(&table_def_cache, idx);
@@ -939,14 +1451,14 @@ OPEN_TABLE_LIST *list_open_tables(THD *t
 		  share->db.str)+1,
 	   share->table_name.str);
     (*start_list)->in_use= 0;
-    TABLE_SHARE::TABLE_list::Iterator it(share->used_tables);
+    Table_cache_iterator it(share);
     while (it++)
       ++(*start_list)->in_use;
     (*start_list)->locked= 0;                   /* Obsolete. */
     start_list= &(*start_list)->next;
     *start_list=0;
   }
-  mysql_mutex_unlock(&LOCK_open);
+  table_cache_manager.unlock_all_and_tdc();
   DBUG_RETURN(open_list);
 }
 
@@ -967,33 +1479,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 *table)
-{
-  DBUG_ENTER("free_cache_entry");
-
-  /* This should be done before releasing table share. */
-  table_def_remove_table(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)
@@ -1015,15 +1505,15 @@ void free_io_cache(TABLE *table)
 
    @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_SHARE::TABLE_list::Iterator it(share->used_tables);
-  TABLE *tab;
+  table_cache_manager.assert_owner_all();
 
-  mysql_mutex_assert_owner(&LOCK_open);
+  Table_cache_iterator it(share);
+  TABLE *tab;
 
   while ((tab= it++))
   {
@@ -1046,6 +1536,32 @@ static void kill_delayed_threads_for_tab
 }
 
 
+/** Free all unused TABLE objects in the table cache. */
+
+void Table_cache::free_all_unused_tables()
+{
+  assert_owner();
+
+  while (m_unused_tables)
+  {
+    TABLE *table_to_free= m_unused_tables;
+    remove_table(table_to_free);
+    intern_close_table(table_to_free);
+  }
+}
+
+
+/** Free all unused TABLE objects in all table cache instances. */
+
+void Table_cache_manager::free_all_unused_tables()
+{
+  assert_owner_all_and_tdc();
+
+  for (uint i= 0; i < table_cache_instances; i++)
+    m_table_cache[i].free_all_unused_tables();
+}
+
+
 /*
   Close all tables which aren't in use by any thread
 
@@ -1074,7 +1590,7 @@ bool close_cached_tables(THD *thd, TABLE
   DBUG_ENTER("close_cached_tables");
   DBUG_ASSERT(thd || (!wait_for_refresh && !tables));
 
-  mysql_mutex_lock(&LOCK_open);
+  table_cache_manager.lock_all_and_tdc();
   if (!tables)
   {
     /*
@@ -1094,8 +1610,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".
     */
-    while (unused_tables)
-      free_cache_entry(unused_tables);
+    table_cache_manager.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);
@@ -1120,7 +1635,7 @@ bool close_cached_tables(THD *thd, TABLE
       wait_for_refresh=0;			// Nothing to wait for
   }
 
-  mysql_mutex_unlock(&LOCK_open);
+  table_cache_manager.unlock_all_and_tdc();
 
   if (!wait_for_refresh)
     DBUG_RETURN(result);
@@ -1409,7 +1924,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);
 }
 
 
@@ -1624,9 +2139,8 @@ 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)
 {
-  bool found_old_table= 0;
   TABLE *table= *table_ptr;
   DBUG_ENTER("close_thread_table");
   DBUG_ASSERT(table->key_read == 0);
@@ -1659,27 +2173,23 @@ bool close_thread_table(THD *thd, TABLE
   if (table->file != NULL)
     table->file->unbind_psi();
 
-  mysql_mutex_lock(&LOCK_open);
+  Table_cache *tc= table_cache_manager.get_cache(thd);
+
+  tc->lock();
 
   if (table->s->has_old_version() || table->needs_reopen() ||
       table_def_shutdown_in_progress)
   {
-    free_cache_entry(table);
-    found_old_table= 1;
+    tc->remove_table(table);
+    mysql_mutex_lock(&LOCK_open);
+    intern_close_table(table);
+    mysql_mutex_unlock(&LOCK_open);
   }
   else
-  {
-    DBUG_ASSERT(table->file);
-    table_def_unuse_table(table);
-    /*
-      We free the least used table, not the subject table,
-      to keep the LRU order.
-    */
-    if (table_cache_count > table_cache_size)
-      free_cache_entry(unused_tables);
-  }
-  mysql_mutex_unlock(&LOCK_open);
-  DBUG_RETURN(found_old_table);
+    tc->release_table(thd, table);
+
+  tc->unlock();
+  DBUG_VOID_RETURN;
 }
 
 
@@ -2736,7 +3246,6 @@ bool open_table(THD *thd, TABLE_LIST *ta
   int error;
   TABLE_SHARE *share;
   my_hash_value_type hash_value;
-  bool recycled_free_table;
 
   DBUG_ENTER("open_table");
 
@@ -2971,6 +3480,90 @@ bool open_table(THD *thd, TABLE_LIST *ta
     DBUG_RETURN(FALSE);
 
 retry_share:
+  {
+    Table_cache *tc= table_cache_manager.get_cache(thd);
+
+    tc->lock();
+
+    /*
+      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 (table)
+    {
+      /* We have found an unused TABLE object. */
+
+      if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
+      {
+        /*
+          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.
+
+          Table cache should not contain any unused TABLE objects with
+          old versions.
+        */
+        DBUG_ASSERT(!share->has_old_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 != share->version)
+        {
+          tc->release_table(thd, table);
+          tc->unlock();
+          (void)ot_ctx->request_backoff_action(
+                          Open_table_context::OT_REOPEN_TABLES,
+                          NULL);
+          DBUG_RETURN(TRUE);
+        }
+      }
+      tc->unlock();
+
+      /* 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();
+    }
+  }
 
   mysql_mutex_lock(&LOCK_open);
 
@@ -3056,6 +3649,7 @@ retry_share:
     goto err_unlock;
   }
 
+share_found:
   if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
   {
     if (share->has_old_version())
@@ -3069,12 +3663,12 @@ retry_share:
         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,
@@ -3104,71 +3698,55 @@ retry_share:
     }
   }
 
-  if (!share->free_tables.is_empty())
-  {
-    table= share->free_tables.front();
-    table_def_use_table(thd, table);
-    recycled_free_table= true;
-    /* We need to release share as we have EXTRA reference to it in our hands. */
-    release_table_share(share);
-  }
-  else
-  {
-    /* We have too many TABLE instances around let us try to get rid of them. */
-    while (table_cache_count > table_cache_size && unused_tables)
-      free_cache_entry(unused_tables);
-
-    mysql_mutex_unlock(&LOCK_open);
-
-    recycled_free_table= false;
-    /* make a new table */
-    if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
-      goto err_lock;
+  mysql_mutex_unlock(&LOCK_open);
 
-    error= open_table_from_share(thd, share, alias,
-                                 (uint) (HA_OPEN_KEYFILE |
-                                         HA_OPEN_RNDFILE |
-                                         HA_GET_INDEX |
-                                         HA_TRY_READ_ONLY),
-                                 (READ_KEYINFO | COMPUTE_TYPES |
-                                  EXTRA_RECORD),
-                                 thd->open_options, table, FALSE);
+  /* make a new table */
+  if (!(table= (TABLE*) my_malloc(sizeof(*table), MYF(MY_WME))))
+    goto err_lock;
+
+  error= open_table_from_share(thd, share, alias,
+                               (uint) (HA_OPEN_KEYFILE |
+                                       HA_OPEN_RNDFILE |
+                                       HA_GET_INDEX |
+                                       HA_TRY_READ_ONLY),
+                               (READ_KEYINFO | COMPUTE_TYPES |
+                                EXTRA_RECORD),
+                               thd->open_options, table, FALSE);
 
-    if (error)
-    {
-      my_free(table);
+  if (error)
+  {
+    my_free(table);
 
-      if (error == 7)
-        (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER,
-                                              table_list);
-      else if (share->crashed)
-        (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR,
-                                              table_list);
+    if (error == 7)
+      (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER,
+                                            table_list);
+    else if (share->crashed)
+      (void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR,
+                                            table_list);
+    goto err_lock;
+  }
+  if (open_table_entry_fini(thd, share, table))
+  {
+    closefrm(table, 0);
+    my_free(table);
+    goto err_lock;
+  }
+  {
+    /* Add new TABLE object to table cache for this connection. */
+    Table_cache *tc= table_cache_manager.get_cache(thd);
 
-      goto err_lock;
-    }
+    tc->lock();
 
-    if (open_table_entry_fini(thd, share, table))
+    if (tc->add_used_table(thd, table))
     {
-      closefrm(table, 0);
-      my_free(table);
+      tc->unlock();
       goto err_lock;
     }
-
-    mysql_mutex_lock(&LOCK_open);
-    /* Add table to the share's used tables list. */
-    table_def_add_used_table(thd, table);
-  }
-
-  mysql_mutex_unlock(&LOCK_open);
-
-  /* Call rebind_psi outside of the LOCK_open critical section. */
-  if (recycled_free_table)
-  {
-    DBUG_ASSERT(table->file != NULL);
-    table->file->rebind_psi();
+    tc->unlock();
   }
+  thd->status_var.table_open_cache_misses++;
 
+table_found:
   table->mdl_ticket= mdl_ticket;
 
   table->next= thd->open_tables;		/* Link into simple list */
@@ -3179,7 +3757,7 @@ retry_share:
  reset:
   table->created= TRUE;
   /*
-    Check that there is no reference to a condtion from an earlier query
+    Check that there is no reference to a condition from an earlier query
     (cf. Bug#58553). 
   */
   DBUG_ASSERT(table->file->pushed_cond == NULL);
@@ -4002,12 +4580,14 @@ static bool auto_repair_table(THD *thd,
   }
   my_free(entry);
 
-  mysql_mutex_lock(&LOCK_open);
+  table_cache_manager.lock_all_and_tdc();
   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);
+  table_cache_manager.unlock_all_and_tdc();
+  return result;
 end_unlock:
   mysql_mutex_unlock(&LOCK_open);
   return result;
@@ -9206,10 +9786,69 @@ my_bool mysql_rm_tmp_tables(void)
 
 void tdc_flush_unused_tables()
 {
-  mysql_mutex_lock(&LOCK_open);
-  while (unused_tables)
-    free_cache_entry(unused_tables);
-  mysql_mutex_unlock(&LOCK_open);
+  table_cache_manager.lock_all_and_tdc();
+  table_cache_manager.free_all_unused_tables();
+  table_cache_manager.unlock_all_and_tdc();
+}
+
+
+/**
+   Remove and free 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  share        TABLE_SHARE for the table to be removed.
+
+   @note Caller should own LOCK_open and locks on all table cache
+         instances.
+*/
+void Table_cache_manager::free_table(THD *thd,
+                                     enum_tdc_remove_table_type remove_type,
+                                     TABLE_SHARE *share)
+{
+  Table_cache_element *cache_el[MAX_TABLE_CACHES];
+
+  assert_owner_all_and_tdc();
+
+  /*
+    Freeing last TABLE instance for the share will destroy the share
+    and corresponding TABLE_SHARE::cache_element[] array. To make
+    iteration over this array safe, even when share is destroyed in
+    the middle of iteration, we create copy of this array on the stack
+    and iterate over it.
+  */
+  memcpy(&cache_el, share->cache_element,
+         table_cache_instances * sizeof(Table_cache_element *));
+
+  for (uint i= 0; i < table_cache_instances; i++)
+  {
+    if (cache_el[i])
+    {
+      Table_cache_element::TABLE_list::Iterator it(cache_el[i]->free_tables);
+      TABLE *table;
+
+#ifndef DBUG_OFF
+      if (remove_type == TDC_RT_REMOVE_ALL)
+        DBUG_ASSERT(cache_el[i]->used_tables.is_empty());
+      else if (remove_type == TDC_RT_REMOVE_NOT_OWN)
+      {
+        Table_cache_element::TABLE_list::Iterator it2(cache_el[i]->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);
+      }
+    }
+  }
 }
 
 
@@ -9249,15 +9888,12 @@ void tdc_remove_table(THD *thd, enum_tdc
 {
   char key[MAX_DBKEY_LENGTH];
   uint key_length;
-  TABLE *table;
   TABLE_SHARE *share;
 
   if (! has_lock)
-    mysql_mutex_lock(&LOCK_open);
+    table_cache_manager.lock_all_and_tdc();
   else
-  {
-    mysql_mutex_assert_owner(&LOCK_open);
-  }
+    table_cache_manager.assert_owner_all_and_tdc();
 
   DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED ||
               thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name,
@@ -9270,22 +9906,6 @@ void tdc_remove_table(THD *thd, enum_tdc
   {
     if (share->ref_count)
     {
-      TABLE_SHARE::TABLE_list::Iterator it(share->free_tables);
-#ifndef DBUG_OFF
-      if (remove_type == TDC_RT_REMOVE_ALL)
-      {
-        DBUG_ASSERT(share->used_tables.is_empty());
-      }
-      else if (remove_type == TDC_RT_REMOVE_NOT_OWN)
-      {
-        TABLE_SHARE::TABLE_list::Iterator it2(share->used_tables);
-        while ((table= it2++))
-          if (table->in_use != thd)
-          {
-            DBUG_ASSERT(0);
-          }
-      }
-#endif
       /*
         Set share's version to zero in order to ensure that it gets
         automatically deleted once it is no longer referenced.
@@ -9298,16 +9918,14 @@ void tdc_remove_table(THD *thd, enum_tdc
         used.
       */
       share->version= 0;
-
-      while ((table= it++))
-        free_cache_entry(table);
+      table_cache_manager.free_table(thd, remove_type, share);
     }
     else
       (void) my_hash_delete(&table_def_cache, (uchar*) share);
   }
 
   if (! has_lock)
-    mysql_mutex_unlock(&LOCK_open);
+    table_cache_manager.unlock_all_and_tdc();
 }
 
 

=== modified file 'sql/sql_base.h'
--- a/sql/sql_base.h	2012-03-08 14:22:07 +0000
+++ b/sql/sql_base.h	2012-05-21 17:56:02 +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;
@@ -612,4 +610,193 @@ private:
 };
 
 
+/**
+  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
+{
+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) m_table_count - total number of TABLE objects in this cache.
+    5) the element in TABLE_SHARE::cache_element[] array that corresponds
+       to this cache,
+    6) in_use member in TABLE object.
+    7) 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 *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;
+
+private:
+
+#ifdef EXTRA_DEBUG
+  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);
+
+public:
+
+  bool init();
+  void destroy();
+
+  /** Acquire lock on table cache instance. */
+  void lock() { mysql_mutex_lock(&m_lock); }
+  /** Release lock on table cache instance. */
+  void unlock() { mysql_mutex_unlock(&m_lock); }
+  /** Assert that caller owns lock on the table cache. */
+  void assert_owner() { mysql_mutex_assert_owner(&m_lock); }
+
+  inline TABLE* get_table(THD *thd, my_hash_value_type hash_value,
+                          const char *key, uint key_length,
+                          TABLE_SHARE **share);
+
+  inline void release_table(THD *thd, TABLE *table);
+
+  inline bool add_used_table(THD *thd, TABLE *table);
+  inline void remove_table(TABLE *table);
+
+  /** Get number of TABLE instances in the cache. */
+  uint cached_tables() const { return m_table_count; }
+
+  void free_all_unused_tables();
+
+#ifndef DBUG_OFF
+  void print_tables();
+#endif
+};
+
+
+/**
+  Container class for all table cache instances in the system.
+*/
+
+class Table_cache_manager
+{
+public:
+
+  /** Maximum supported number of table cache instances. */
+  static const int MAX_TABLE_CACHES= 64;
+
+  bool init();
+  void destroy();
+
+  /** Get instance of table cache to be used by particular connection. */
+  Table_cache* get_cache(THD *thd)
+  {
+    return &m_table_cache[thd->thread_id % table_cache_instances];
+  }
+
+  /** Get index for the table cache in container. */
+  uint cache_index(Table_cache *cache) const
+  {
+    return (cache - &m_table_cache[0]);
+  }
+
+  uint cached_tables();
+
+  void lock_all_and_tdc();
+  void unlock_all_and_tdc();
+  void assert_owner_all();
+  void assert_owner_all_and_tdc();
+
+  void free_table(THD *thd,
+                  enum_tdc_remove_table_type remove_type,
+                  TABLE_SHARE *share);
+
+  void free_all_unused_tables();
+
+#ifndef DBUG_OFF
+  void print_tables();
+#endif
+
+  friend class Table_cache_iterator;
+
+private:
+
+  /**
+    An array of Table_cache instances.
+    Only the first table_cache_instances elements in it are used.
+  */
+  Table_cache m_table_cache[MAX_TABLE_CACHES];
+};
+
+
+/**
+  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();
+};
+
+
+extern Table_cache_manager table_cache_manager;
+
 #endif /* SQL_BASE_INCLUDED */

=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h	2012-05-18 14:39:25 +0000
+++ b/sql/sql_class.h	2012-05-21 17:56:02 +0000
@@ -566,6 +566,9 @@ typedef struct system_status_var
   ulonglong ha_external_lock_count;
   ulonglong opened_tables;
   ulonglong opened_shares;
+  ulonglong table_open_cache_hits;
+  ulonglong table_open_cache_misses;
+  ulonglong table_open_cache_overflows;
   ulonglong select_full_join_count;
   ulonglong select_full_range_join_count;
   ulonglong select_range_count;

=== 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-21 17:56:02 +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-05-17 12:55:26 +0000
+++ b/sql/sql_parse.cc	2012-05-21 17:56:02 +0000
@@ -1576,7 +1576,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_manager.cached_tables(),
                         (uint) (queries_per_second1000 / 1000),
                         (uint) (queries_per_second1000 % 1000));
 #ifdef EMBEDDED_LIBRARY

=== modified file 'sql/sql_partition.cc'
--- a/sql/sql_partition.cc	2012-05-09 16:47:39 +0000
+++ b/sql/sql_partition.cc	2012-05-21 17:56:02 +0000
@@ -6552,7 +6552,6 @@ void handle_alter_part_error(ALTER_PARTI
       }
     }
     /* Ensure the share is destroyed and reopened. */
-    table->s->version= 0;
     part_info= lpt->part_info->get_clone();
     close_all_tables_for_name(thd, table->s, false);
   }

=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc	2012-05-16 11:03:43 +0000
+++ b/sql/sql_table.cc	2012-05-21 17:56:02 +0000
@@ -7662,10 +7662,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-30 12:42:56 +0000
+++ b/sql/sql_test.cc	2012-05-21 17:56:02 +0000
@@ -41,7 +41,7 @@
 
 #include "global_threads.h"
 
-static const char *lock_descriptions[] =
+const char *lock_descriptions[TL_WRITE_ONLY + 1] =
 {
   /* TL_UNLOCK                  */  "No lock",
   /* TL_READ_DEFAULT            */  NULL,
@@ -82,61 +82,16 @@ print_where(Item *cond,const char *info,
 
 static void print_cached_tables(void)
 {
-  uint idx,count,unused;
-  TABLE_SHARE *share;
-  TABLE *start_link, *lnk, *entry;
-
-  compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions));
-
   /* purecov: begin tested */
-  mysql_mutex_lock(&LOCK_open);
-  puts("DB             Table                            Version  Thread  Open  Lock");
+  table_cache_manager.lock_all_and_tdc();
 
-  for (idx=unused=0 ; idx < table_def_cache.records ; idx++)
-  {
-    share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx);
+  table_cache_manager.print_tables();
 
-    TABLE_SHARE::TABLE_list::Iterator it(share->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(share->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");
-    }
-  }
-  count=0;
-  if ((start_link=lnk=unused_tables))
-  {
-    do
-    {
-      if (lnk != lnk->next->prev || lnk != lnk->prev->next)
-      {
-	printf("unused_links isn't linked properly\n");
-	return;
-      }
-    } while (count++ < cached_open_tables() && (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);
+  table_cache_manager.unlock_all_and_tdc();
   /* purecov: end */
   return;
 }
@@ -435,7 +390,8 @@ 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_manager.cached_tables() + 20,50);
   mysql_mutex_lock(&THR_LOCK_lock);
   for (list= thr_lock_thread_list; list; list= list_rest(list))
   {
@@ -557,7 +513,7 @@ Open tables:   %10lu\n\
 Open files:    %10lu\n\
 Open streams:  %10lu\n",
 	 (ulong) tmp.opened_tables,
-	 (ulong) cached_open_tables(),
+	 (ulong) table_cache_manager.cached_tables(),
 	 (ulong) my_file_opened,
 	 (ulong) my_stream_opened);
 

=== modified file 'sql/sql_test.h'
--- a/sql/sql_test.h	2011-09-19 11:29:21 +0000
+++ b/sql/sql_test.h	2012-05-21 17:56:02 +0000
@@ -24,6 +24,8 @@ struct TABLE_LIST;
 typedef class st_select_lex SELECT_LEX;
 typedef struct st_sort_field SORT_FIELD;
 
+extern const char *lock_descriptions[TL_WRITE_ONLY + 1];
+
 #ifndef DBUG_OFF
 void print_where(Item *cond,const char *info, enum_query_type query_type);
 void TEST_join(JOIN *join);

=== modified file 'sql/sys_vars.cc'
--- a/sql/sys_vars.cc	2012-05-03 09:41:32 +0000
+++ b/sql/sys_vars.cc	2012-05-21 17:56:02 +0000
@@ -2752,10 +2752,29 @@ static Sys_var_ulong Sys_table_def_size(
        VALID_RANGE(TABLE_DEF_CACHE_MIN, 512*1024),
        DEFAULT(TABLE_DEF_CACHE_DEFAULT), BLOCK_SIZE(1));
 
+static bool fix_table_cache_size(sys_var *self, THD *thd, enum_var_type type)
+{
+  /*
+    table_open_cache parameter is a soft limit for total number of objects
+    in all table cache instances. Once this value is updated we need to
+    update value of a per-instance soft limit on table cache size.
+  */
+  table_cache_size_per_instance= table_cache_size / table_cache_instances;
+  return false;
+}
+
 static Sys_var_ulong Sys_table_cache_size(
-       "table_open_cache", "The number of cached open tables",
+       "table_open_cache", "The number of cached open tables "
+       "(total for all table cache instances)",
        GLOBAL_VAR(table_cache_size), CMD_LINE(REQUIRED_ARG),
        VALID_RANGE(1, 512*1024), DEFAULT(TABLE_OPEN_CACHE_DEFAULT),
+       BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL),
+       ON_UPDATE(fix_table_cache_size));
+
+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, Table_cache_manager::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-05-15 09:39:47 +0000
+++ b/sql/table.cc	2012-05-21 17:56:02 +0000
@@ -322,6 +322,7 @@ TABLE_SHARE *alloc_table_share(TABLE_LIS
   char *key_buff, *path_buff;
   char path[FN_REFLEN];
   uint path_length;
+  Table_cache_element **cache_element_array;
   DBUG_ENTER("alloc_table_share");
   DBUG_PRINT("enter", ("table: '%s'.'%s'",
                        table_list->db, table_list->table_name));
@@ -334,6 +335,8 @@ TABLE_SHARE *alloc_table_share(TABLE_LIS
                        &share, sizeof(*share),
                        &key_buff, key_length,
                        &path_buff, path_length + 1,
+                       &cache_element_array,
+                       table_cache_instances * sizeof(*cache_element_array),
                        NULL))
   {
     memset(share, 0, sizeof(*share));
@@ -358,10 +361,12 @@ TABLE_SHARE *alloc_table_share(TABLE_LIS
     share->table_map_id= ~0UL;
     share->cached_row_logging_check= -1;
 
-    share->used_tables.empty();
-    share->free_tables.empty();
     share->m_flush_tickets.empty();
 
+    memset(cache_element_array, 0,
+           table_cache_instances * sizeof(*cache_element_array));
+    share->cache_element= cache_element_array;
+
     memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root));
     mysql_mutex_init(key_TABLE_SHARE_LOCK_ha_data,
                      &share->LOCK_ha_data, MY_MUTEX_INIT_FAST);
@@ -423,8 +428,6 @@ void init_tmp_table_share(THD *thd, TABL
   */
   share->table_map_id= (ulong) thd->query_id;
 
-  share->used_tables.empty();
-  share->free_tables.empty();
   share->m_flush_tickets.empty();
 
   DBUG_VOID_RETURN;
@@ -3259,6 +3262,7 @@ bool TABLE_SHARE::visit_subgraph(Wait_fo
   TABLE *table;
   MDL_context *src_ctx= wait_for_flush->get_ctx();
   bool result= TRUE;
+  bool locked= FALSE;
 
   /*
     To protect used_tables list from being concurrently modified
@@ -3268,9 +3272,12 @@ bool TABLE_SHARE::visit_subgraph(Wait_fo
     holding a write-lock on MDL_lock::m_rwlock.
   */
   if (gvisitor->m_lock_open_count++ == 0)
-    mysql_mutex_lock(&LOCK_open);
+  {
+    locked= TRUE;
+    table_cache_manager.lock_all_and_tdc();
+  }
 
-  TABLE_SHARE::TABLE_list::Iterator tables_it(used_tables);
+  Table_cache_iterator tables_it(this);
 
   /*
     In case of multiple searches running in parallel, avoid going
@@ -3309,8 +3316,12 @@ end_leave_node:
   gvisitor->leave_node(src_ctx);
 
 end:
-  if (gvisitor->m_lock_open_count-- == 1)
-    mysql_mutex_unlock(&LOCK_open);
+  gvisitor->m_lock_open_count--;
+  if (locked)
+  {
+    DBUG_ASSERT(gvisitor->m_lock_open_count == 0);
+    table_cache_manager.unlock_all_and_tdc();
+  }
 
   return result;
 }

=== modified file 'sql/table.h'
--- a/sql/table.h	2012-05-11 12:05:39 +0000
+++ b/sql/table.h	2012-05-21 17:56:02 +0000
@@ -46,6 +46,7 @@ class ACL_internal_schema_access;
 class ACL_internal_table_access;
 class Field;
 class Field_temporal_with_date_and_time;
+struct Table_cache_element;
 
 /*
   Used to identify NESTED_JOIN structures within a join (applicable only to
@@ -503,8 +504,6 @@ TABLE_CATEGORY get_table_category(const
                                   const LEX_STRING *name);
 
 
-struct TABLE_share;
-
 extern ulong refresh_version;
 
 typedef struct st_table_field_type
@@ -595,14 +594,15 @@ 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 */
-
-  /*
-    Doubly-linked (back-linked) lists of used and unused TABLE objects
-    for this share.
+  /**
+    Array of table_cache_instances pointers to elements of table caches
+    respresenting this table in each of Table_cache instances.
+    Allocated along with the share itself in alloc_table_share().
+    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.
   */
-  typedef I_P_List <TABLE, TABLE_share> TABLE_list;
-  TABLE_list used_tables;
-  TABLE_list free_tables;
+  Table_cache_element **cache_element;
 
   /* The following is copied to each TABLE on OPEN */
   Field **field;
@@ -922,13 +922,19 @@ struct TABLE
 
 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;
 
-  friend struct TABLE_share;
+  /*
+    Give Table_cache_element access to the above two members to allow
+    using them for linking TABLE objects in a list.
+  */
+  friend struct Table_cache_element;
 
   Field_temporal_with_date_and_time *timestamp_field;
 
@@ -1176,24 +1182,6 @@ public:
 };
 
 
-/**
-   Helper class which specifies which members of TABLE are used for
-   participation in the list of used/unused TABLE objects for the share.
-*/
-
-struct TABLE_share
-{
-  static inline TABLE **next_ptr(TABLE *l)
-  {
-    return &l->share_next;
-  }
-  static inline TABLE ***prev_ptr(TABLE *l)
-  {
-    return &l->share_prev;
-  }
-};
-
-
 enum enum_schema_table_state
 { 
   NOT_PROCESSED= 0,

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