MySQL 的关键指标之一是实例内存使用率和缓冲池命中率。高内存使用率会增加 OOM(内存溢出)错误的风险,而缓冲池命中率低则表明许多数据页未命中缓冲池中的缓存数据页,需要从存储读取数据,导致 IO 吞吐量和延迟增加。
注意:由于数据更新频率、缓存及内核使用(这部分不计入)的限制,该语句仅能大致反映内存使用情况。
MySQL 支持使用分号 ; 分隔多个 SQL 语句,一起发送给 MySQL 依次处理。但部分内存需要等待所有 SQL 语句执行完成后才能释放。
如果一次发送大量 SQL 语句,比如达到数百兆字节,那么在 SQL 语句执行过程中,各种对象的分配和累计消耗可能非常大,可能导致 MySQL 进程内存耗尽。
此外,使用多语句发送 SQL 还可能导致网络流量突增,可通过网络流量监控和 SQL 透视进行评估。因此,在业务实现中建议尽量避免使用多语句 SQL 发送方式。
所有表的数据页都存储在缓冲池中。查询执行时,如果所需数据页直接命中缓冲池,则不会发生物理 IO,从而实现更高的 SQL 执行效率。缓冲池采用 LRU(最近最少使用)算法管理数据页,所有脏页放置在 Flush List 链表中。
在大内存系统中,InnoDB Buffer Pool 内存通常是实例内存中最大的部分。可以通过修改 innodb_buffer_pool_instances 参数增加缓冲池实例数,提高并发性能。
常见的缓冲池相关问题及优化建议如下表所示。
| 问题描述 | 原因或建议 |
|---|---|
| 数据页预热不足导致查询延迟较高 | 通常发生在实例重启后、冷数据读取或缓冲池命中率较低时。建议升级实例规格或提前进行数据预热。 |
| 脏页积累过多 | 当脏页未及时刷新,后台线程会触发同步刷新脏页,严重影响实例性能。解决方案包括平衡写负载,避免过高写入吞吐,调整刷新脏页参数,或升级实例规格。 |
| 全表扫描污染缓冲池 | 应避免全表扫描,例如避免使用 select * from large_table 语句,以减少对缓冲池的污染。 |
MySQL 中使用内存临时表时,其大小受 tmp_table_size 和 max_heap_table_size 参数限制。超过该参数限制的临时表会转换为磁盘临时表。如果大量连接短时间内创建大量内存临时表,可能导致内存使用急剧上升。
MySQL 8.0 引入了新的临时表引擎,该引擎要求所有线程分配的内存临时表总大小必须小于 temptable_max_ram 参数,默认值为 1GB。超过该限制时,内存临时表将转换为磁盘临时表。
使用 MGR(MySQL Group Replication)组建集群时,还需考虑 XCom Cache 通信缓存的消耗。该缓存默认大小为 1GB。在网络质量较好的环境下,可以使用较低的 group_replication_message_cachesize 参数创建集群,从而降低内存使用。
当实例中表数量特别多或查询频率非常高时,Table Cache 会占用大量内存。建议避免在实例中创建过多表,或将 table_open_cache 参数设置为合适大小。
AHI(Adaptive Hash Index)默认占用缓冲池内存的 1/64。如果查询或写入大量大字段(如 BLOB),内存会动态分配,增加内存使用。禁用 AHI 可以更有效合理地利用服务器内存资源,释放更多可用内存给业务系统,从而提升整体性能。但需注意,AHI 也能加速特定查询,禁用时需权衡查询性能提升与内存优化之间的关系。
如果 MySQL 实例出现异常内存使用增长或内存耗尽,建议参考 MySQL 5.7 官方文档 或 MySQL 8.0 官方文档 调查内存使用增长原因。