改进MySQL的table_cache

本文详细探讨了MySQL table_cache参数的工作原理及优化方法。通过分析MySQL内部机制,解释了设置table_cache参数过大会导致的效率下降,并介绍了buglist上提供的一个patch,通过在table实例中增加三个指针,将扫描open_cache哈希表的时间复杂度降低至O(1)。文章进一步阐述了打开表时的处理流程,包括如何在不同类型的表(如临时表)间进行选择,以及如何利用全局锁来保护操作过程中的并发安全。最后,文章分享了针对table_cache参数优化的建议,旨在帮助提升MySQL数据库的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下为本人在工作中的碎碎念,记录的比较凌乱……

........................................................................

在mysql里面有一个参数table_cache,当设置过大时,会产生明显的效率下降。这是因为扫描open_cache哈希表时,使用的线性扫描,时间复杂度为O(n),mysql的bug list上有人提供了一个patch(http://bugs.mysql.com/bug.php?id=33948),可以把时间降到o(1),其基本思想是为table实例增加三个指针,来维护一个空闲链表。


首先,我们分析一下mysql在打开一个表时如何工作:


在mysql里,table_cache是一个比较重要的参数。由于多线程机制,每个线程独自打开自己需要的标的文件描述符,而不是共享已经打开的。




1. table_cache key (见create_table_def_key)
在内存里,table cache使用hash表来存储,key为 database_name\0table_name\0+(可选的,用于临时表)

这里对于临时表会做特殊处理,需要增加额外的信息来保证临时表在slave端是唯一的
增加8个字节:前4个字节为master thread id,后4个字节为slavb


2.打开表时候的处理:open_table


***************
必要的检查:线程栈是否足够,thd是否被kill
**************
全局锁:lock_open
*************************
首先判断是否是临时表
*************************
这里有一段很有意思的逻辑,当需要打开表时,总是先从临时表链表中查找表。也就是说,当存在一个与实际表同名的临时表时,会总是操作临时表
if (!table_list->skip_temporary)
{
for (table= thd->temporary_tables; table ; table=table->next)
{


**********************************************
非临时表,且处于pre-locked 或 lock_tables mode(thd->locked_tables || thd->prelocked_mode)
即该线程已经打开或锁定了一些表,从thd->open_tables里查询,当不存在时,返回error
**********************************************
if (thd->locked_tables || thd->prelocked_mode)
{ // Using table locks
TABLE *best_table= 0;
int best_distance= INT_MIN;
for (table=thd->open_tables; table ; table=table->next)
{



*******************************************************
正常情况:
1. 首先尝试从table cache中取table
2. 当找到的TABLE实例是nam-locked的,或者一些线程正在flush tables,我们需要等待,直到锁释放
3. 如果不存在这样的TABLE,我们需要创建TABLE,并将其加入到cache中
!这些操作都需要全局锁:LOCK_open,来保护table cache和磁盘上的表定义
*******************************************************

如果这是该query打开的第一个表:设置thd->version = refresh_version,这样,当我们打开剩余表的过程中,如果 version发生了变化,则需要back off,关闭所有已经打开的并重新打开表
目前refresh_version只会被FLUSH TABLES命令改变

if (thd->handler_tables)
mysql_ha_flush(thd); //刷新(关闭并标记为re-open)所有需要reopen的表


查询table cache的过程:

for (table= (TABLE*) hash_first(&open_cache, (uchar*) key, key_length, //基于同一个key来查找hash表
&state);
table && table->in_use ;
table= (TABLE*) hash_next(&open_cache, (uchar*) key, key_length,
&state))
{

**********************************
flush tables marked for flush.
Normally, table->s->version contains the value of
refresh_version from the moment when this table was
(re-)opened and added to the cache.
If since then we did (or just started) FLUSH TABLES
statement, refresh_version has been increased.
For "name-locked" TABLE instances, table->s->version is set
to 0 (see lock_table_name for details).
In case there is a pending FLUSH TABLES or a name lock, we
need to back off and re-start opening tables.
If we do not back off now, we may dead lock in case of lock
order mismatch with some other thread:
c1: name lock t1; -- sort of exclusive lock
c2: open t2; -- sort of shared lock
c1: name lock t2; -- blocks
c2: open t1; -- blocks
*********************************

if (table->needs_reopen_or_name_lock()) //Is this instance of the table should be reopen or represents a name-lock?
{}

}

if (table)
************
从unused_tables链表中移除刚找到的table
************
else
***********
创建一个新的table实例,并插入到open cache中
***********
while (open_cache.records > table_cache_size && unused_tables) //当cache满时,从中释放未使用的TABLE实例
hash_delete(&open_cache,(uchar*) unused_tables);

if (table_list->create) //创建一个新表
{

*******
检查表是否存在:check_if_table_exists
*******
在table cache的hash中创建一个placeholder(占位符):table_cache_insert_placeholder
将占位符链到open tables list上:
table->open_placeholder= 1;
table->next= thd->open_tables;
thd->open_tables= table;

return table
}

创建一个新的table实例
分配内存table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))

error= open_unireg_entry(thd, table, table_list, alias, key, key_length,
mem_root, (flags & OPEN_VIEW_NO_PARSE));


如果是视图 or error < 0 释放内存,返回;


my_hash_insert(&open_cache,(uchar*) table)


------------------------------------------------

patch:http://bugs.mysql.com/bug.php?id=33948
增加3个指针:
hash_head:
hash_prev: always point to unused table cached items
hash_next: always point to used table cached items

修改的函数:

free_cache_entry //释放一个表的内存。
close_thread_table //move one table to free list
reopen_name_locked_table // 重新打开表,保持链表结构
table_cache_insert_placeholder
open_table
------------------------------------------------------------------------
总结:

增加了三个指针:
hash_head:
hash_prev:
hash_next:

!.............................!head!.........................!

head的左边为空闲item链表
head的右边为占用的item链表
所有item通过hash_prev和hash_next进行双向指针
右边的item的hash_head指向head


操作链表:
1)插入新空闲item:在head节点前加入
2)插入新的被占用item:在head后面加入
3)从链表中删除item:
---若该item为head,修改head右侧的item的hash_head指向head->next
---否则,直接删除item,并释放内存。。

查询空闲节点:
1) 找到head
2) 检测head是否in_use,为False则table = head, true则找到table = head->prev
3)当table 不为NULL时,表示找到一个item,将其插入到head右侧
3) table依旧为NULL---->创建新item,将其插入到head右侧


------------------------------
转载请注明:印风













<think>我们正在讨论如何正确配置MySQLtable_open_cache参数。根据引用内容,table_open_cacheMySQL中一个重要的参数,它决定了可以同时打开的表的数量。如果设置过小,可能会导致频繁打开和关闭表,影响性能;如果设置过大,可能会占用过多资源,甚至导致文件描述符不足。根据引用[1]:默认的table_open_cache为64,这个数值通常偏小,尤其在高连接数(max_connections较大)的情况下容易引起性能问题。可以通过观察状态变量`Opened_tables`(表示打开表的次数)和`Open_tables`(表示当前打开表的数量)来判断是否需要调整。如果`Opened_tables`的值很大且持续增长,说明需要增大table_open_cache。引用[1]还提到,一般建议将table_open_cache设置为max_connections的值(或者乘以每个查询中平均涉及的表数)。例如,如果max_connections为100,每个查询平均涉及2个表,那么可以设置为200。但实践中,设置为max_connections的值通常就足够了。引用[5]提到,设置过大可能会遇到文件描述符不足的问题(错误23或24)。这时需要调整open_files_limit参数(增加mysqld可打开的文件数)或者减小table_open_cache或max_connections。另外,引用[4]提到,MySQL将全局的tablecache划分为多个Table_cacheinstance,由参数table_open_cache_instances控制。这个参数在MySQL5.6.6及更高版本中引入,用于减少锁竞争。每个instance负责一部分tablecache,用户线程根据thread_id选择instance。通常,建议将table_open_cache_instances设置为1到16之间,但具体设置需要根据实际情况调整。配置步骤:1.查看当前设置:```sqlSHOWVARIABLESLIKE'table_open_cache';SHOWVARIABLESLIKE'max_connections';SHOWVARIABLESLIKE'table_open_cache_instances';```2.查看状态以判断当前设置是否合理:```sqlSHOWGLOBALSTATUSLIKE'Open%tables';```如果`Open_tables`接近table_open_cache,并且`Opened_tables`持续增长,说明需要增大table_open_cache。3.设置新的值(例如设置为1024):```sqlSETGLOBALtable_open_cache=1024;```注意:这个设置是临时的,重启后会失效。要永久生效,需要在MySQL配置文件(如my.cnf或my.ini)中添加或修改:```[mysqld]table_open_cache=1024```4.同时,如果遇到文件描述符不足的问题,需要调整open_files_limit。在配置文件中设置:```[mysqld]open_files_limit=65535```注意:open_files_limit的值应该大于table_open_cache的值(因为MySQL还需要打开其他文件)。5.关于table_open_cache_instances,通常默认值(例如8)在大多数情况下是合适的。如果需要调整,可以在配置文件中设置:```table_open_cache_instances=8```6.修改配置文件后,需要重启MySQL服务。总结:配置table_open_cache的关键是观察`Opened_tables`的状态,将其调整到一个合理的值,避免频繁打开表。同时注意max_connections和open_files_limit的协调。注意:每个系统的情况不同,调整后需要观察性能变化。根据上述内容,生成相关问题:1.如何监控table_open_cache是否设置合理?2.调整table_open_cache后,如何验证效果?3.除了table_open_cache,还有哪些表缓存相关的参数?4.文件描述符不足时,除了调整open_files_limit,还可以如何处理?</think>### 如何正确配置MySQLtable_open_cache参数 配置MySQL的`table_open_cache`参数需综合考虑系统资源、连接数和表使用情况,以下是具体步骤: #### 1. **诊断当前状态** ```sql -- 查看当前缓存设置 SHOW VARIABLES LIKE 'table_open_cache'; -- 检查表打开状态(关键指标) SHOW GLOBAL STATUS LIKE 'Open%tables'; ``` - **Open_tables**:当前打开的表数量(理想值应接近`table_open_cache`)[^1] - **Opened_tables**:累计打开次数(值过高说明缓存不足)[^1] 示例诊断: ``` +---------------+-------+ | Variable_name | Value | +---------------+-------+ | Open_tables | 345 | -- 建议接近table_open_cache值 | Opened_tables | 9,734,116 | -- 过高说明频繁重新打开表 +---------------+-------+ ``` #### 2. **计算合理值** - **基础公式**: ```math \text{推荐值} = \text{max\_connections} \times \text{平均每查询涉及表数} ``` 实践中可简化为: ```math \text{推荐值} \approx \text{max\_connections} \quad [^1] ``` - **上限约束**: 需满足: ```math \text{table\_open\_cache} < \text{open\_files\_limit} - 100 \quad [^5] ``` #### 3. **动态调整** ```sql -- 临时生效(无需重启) SET GLOBAL table_open_cache = 1024; ``` 修改后立即监控`Opened_tables`增长速率是否下降[^1]。 #### 4. **永久生效配置** 在`my.cnf`中添加: ```ini [mysqld] table_open_cache = 1024 # 建议值为max_connections的1-1.5倍 table_open_cache_instances = 8 # 减少锁竞争[^4] open_files_limit = 65535 # 必须大于table_open_cache[^5] ``` #### 5. **优化验证** - 观察指标: - `Opened_tables`增速显著降低 - `SHOW PROCESSLIST`中`Opening tables`状态减少[^1] - 错误处理: 若出现`Can't open file (errno: 24)`,需增加`open_files_limit`[^5] #### 6. **注意事项** - 避免极端值:过大会消耗内存,过小导致性能瓶颈[^3] - 多实例配置:高并发时`table_open_cache_instances`应设为CPU核心数[^4] - 表统计:复杂查询涉及多表时需适当增大缓存 > **配置示例**: > 若`max_connections=500`且无文件限制问题: > ```math > \text{table\_open\_cache} = 500 \sim 750 > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值