背景介绍
Query Cache(查询缓存)是 MySQL 为了提高查询性能而实现的一种缓存策略,基本思想是:对于每个符合条件的查询语句,直接对结果集进行缓存;当下次查询命中时,直接从缓存中取出对应的结果集返回,不需要经过 MySQL 的 Parser / Optimize / Execute / Storage Engine 等复杂的代码执行路径;通过节约 CPU 资源来达到查询加速的目标,是一项非常实用的技术。
但是 MySQL 社区对于 Query Cache 设计和实现上不够理想,存在较多严重的问题,主要包括:
- 并发处理不够好,无法利用多核能力,并发越高性能退化越严重;
- 当缓存命中率较低时,性能无提升甚至会出现严重退化;
- 内存管理问题,内存利用率低并且回收不及时,造成内存浪费;
基于以上问题,MySQL 上的 Query Cache 功能一直没有得到很好的应用,在最新的 MySQL 8.0 版本中,社区直接移除了相关的代码,去掉了此功能。
Fast Query Cache
通过对 Query Cache 的深入分析,阿里云数据库团队对 Query Cache 进行了重新设计,实现了一种更优雅的 Query Cache 机制,从以下几个方面解决了上述问题:
- 优化并发控制:优化全局锁同步机制,重新设计并发场景下的同步问题,采用无锁机制,能够充分利用多核的处理能力,保证高并发场景下的性能;
- 优化缓存机制:动态检测缓存利用率,实时调整缓存策略,解决命中率偏低或读写混合等场景下的性能退化问题;
- 优化内存管理:优化内存预分配机制,采用更加灵活的动态内存分配机制,无效内存及时回收,保证内存的真实利用率;
相比原生的 Query Cache,Fast Query Cache 在保证缓存命中时查询性能的同时,充分控制了缓存带来的副作用,用户可以在不同的业务场景中安心地开启 Fast Query Cache 功能,对查询进行加速。
性能测试
测试环境
- 测试实例规格:RDS MySQL 5.7 版本 4C8G 独享规格;
- 测试工具:sysbench;
- 数据量:25 * 40000,250MB;
- 测试说明:分别测试不同场景下,QC-OFF(关闭 Query Cache),MySQL-QC(MySQL 原生 Query Cache),RDS-QC(Fast Query Cache)的性能差异(QPS);
高命中率场景
测试场景为 oltp_read_only,用例中包含返回单条记录的点查和返回多条记录的范围查询,将 Query Cache 的 query_cache_size
参数设置为 512MB,内存比较充足,整体命中率可以达到 80%+。此场景下主要关注不同并发下 Fast Query Cache 的性能提升效果。
并发数 | QC-OFF | MySQL-QC | RDS-QC | MySQL-QC 性能提升 | RDS-QC 性能提升 |
---|---|---|---|---|---|
1 | 5099 | 6467 | 7022 | 26.83% | 37.71% |
8 | 28782 | 28651 | 45017 | -0.46% | 56.41% |
16 | 35333 | 31099 | 66770 | -11.98% | 88.97% |
32 | 34864 | 27610 | 67623 | -20.81% | 93.96% |
64 | 35503 | 27518 | 75981 | -22.49% | 114.01% |
128 | 35744 | 27733 | 80396 | -22.41% | 124.92% |
256 | 35685 | 27738 | 80925 | -22.27% | 126.78% |
512 | 35308 | 27398 | 79323 | -22.40% | 124.66% |
1024 | 34044 | 26861 | 75742 | -22.10% | 122.48% |
测试结果显示,随着并发数的增加,MySQL 原生 Query Cache 的性能出现明显退化;Fast Query Cache 的性能则会不断提升,峰值性能提升能够达到 120%+。
低命中率场景
测试场景为 oltp_read_only,用例中包含返回单条记录的点查和返回多条记录的范围查询,将 Query Cache 的 query_cache_size
参数设置为 16MB,内存明显不足,缓存命中率只有 10% 左右。此场景下内存不足会导致缓存项的大量淘汰,影响性能,主要关注不同并发下 Fast Query Cache 的性能退化程度。
并发数 | QC-OFF | MySQL-QC | RDS-QC | MySQL-QC 性能提升 | RDS-QC 性能提升 |
---|---|---|---|---|---|
1 | 5004 | 4727 | 5199 | -5.54% | 3.90% |
8 | 28795 | 22542 | 28578 | -21.72% | -0.75% |
16 | 35455 | 24064 | 35682 | -32.13% | 0.64% |
32 | 34526 | 21330 | 35871 | -238.22% | 3.90% |
64 | 35514 | 19791 | 36051 | -44.27% | 1.51% |
128 | 35983 | 19519 | 36253 | -45.75% | 0.75% |
256 | 35695 | 19168 | 36337 | -46.30% | 1.80% |
512 | 35182 | 18420 | 35972 | -47.64% | 2.25% |
1024 | 33915 | 20168 | 34546 | -40.53% | 1.86% |
测试结果显示,内存不足时,MySQL 原生 Query Cache 的性能退化更加明显,最多出现了接近 50% 的性能损失;Fast Query Cache 充分平衡了此种场景,不会带来任何额外的性能损失。
读写混合场景
测试场景为 oltp_read_write,用例中每个事务内都有对表的更新操作将 Query Cache 的 query_cache_size
参数设置为 512MB,内存相对比较充足。此场景下缓存基本处于失效状态,主要关注不同并发下 Fast Query Cache 的性能衰减程度。
并发数 | QC-OFF | RDS-QC | RDS-QC 性能提升 |
---|---|---|---|
1 | 4152 | 4098 | -1.30% |
8 | 21359 | 21195 | -0.77% |
16 | 26020 | 25548 | -1.81% |
32 | 27595 | 26996 | -2.17% |
64 | 29229 | 28733 | -1.70% |
128 | 29265 | 28828 | -1.49% |
256 | 29911 | 29616 | -0.99% |
512 | 29148 | 28816 | -1.14% |
1024 | 29204 | 28824 | -1.30 % |
测试结果显示,Fast Query Cache 在读写混合场景下不会造成过多的性能衰减,整体对性能的影响控制在 2% 以内。
实践指南
适用场景
Fast Query Cache 的目的是提高读操作的性能,所以建议在读多写少的场景下进行开启,或者使用 SQL_CACHE
hint 对读多写少的表单独开启。如果写多读少,数据的更新非常频繁,不建议开启 Query Cache 功能。
需要注意,开启 Fast Query Cache 带来的性能提升和缓存命中率直接相关。在全局开启前建议查看一下 InnoDB Buffer Pool 的命中率情况(100 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests * 100
),如果 Buffer Pool 的命中率低于80%,则不建议开启。表级的读写比可以参照 AliSQL 提供的对象统计功能(TABLE_STATISTICS
)中的数据,对读写比较高的表通过 SQL_CACHE
hint 显式开启 Fast Query Cache。
使用方式
Fast Query Cache 完全兼容 MySQL 原生 Query Cache 的使用逻辑,可通过 query_cache_type
参数控制 Query Cache 的开启方式:
query_cache_type = OFF
,禁用 Query Cache;query_cache_type = ON
,默认使用 Query Cache,可通过SQL_NO_CACHE
hint 跳过缓存;query_cache_type = DEMAND
,默认不启用 Query Cache,通过SQL_CACHE
hint 开启缓存;
query_cache_type
参数支持会话级修改,用户可以根据真实业务场景进行灵活设置:
- 对于更新频繁,写多读少等不适合开启 Query Cache 的场景,应将
query_cache_type
全局设置为 OFF; - 对于数据量较小,访问模式比较固定,且命中率较高的场景,可以将
query_cache_type
全局设置为 ON; - 对于数据量较大,访问模式不固定,且命中率无法保障的场景,可以将
query_cache_type
全局设置为 DEMAND,仅对指定的语句通过SQL_CACHE
hint 使用 Query Cache;
缓存大小设置
Fast Query Cache 的性能提升效果和 Cache 的命中率紧密相关,具体的测试情况如下:
- 测试实例规格:RDS MySQL 5.7 版本 4C8G 独享规格;
- 测试工具:sysbench;
- 数据量:100 * 400000,10GB;
- 测试说明:测试场景为 oltp_point_select,Special 分布(模拟 28 分布,20% 热点),64 并发下测试不同
query_cache_size
大小对于性能的影响;
query_cache_size(MB) | QC-OFF | RDS-QC | RDS-QC 命中率 | RDS-QC 性能提升 |
---|---|---|---|---|
64 | 98236 | 99440 | 22% | 1.23% |
128 | 98236 | 114155 | 45% | 16.21% |
256 | 98236 | 140668 | 72% | 43.19% |
512 | 98236 | 151260 | 82% | 53.98% |
1024 | 98236 | 153866 | 84% | 56.63% |
2048 | 98236 | 159597 | 87% | 62.46% |
4096 | 98236 | 169412 | 92% | 72.45% |
测试结果显示,Fast Query Cache 在各种 query_cache_size
的设置下都不会引起性能退化。由于测试场景是主键点查,所以对缓存命中率要求比较高,Fast Query Cache 的性能提升随着命中率的提高不断提升,当命中率达到 90%+ 时,整体提升效果比较明显。注意:由于阿里云 RDS MySQL 已经对主键点查做了优化,所以 Fast Query Cache 在主键点查下的性能提升不如范围查询明显;对于范围查询或带 ORDER BY
、 GROUP BY
等关键字的查询语句,当缓存命中率低于90%时,也能节约大量的 CPU,带来较大的性能提升。
为了保证 Cache 的命中率,对于 query_cache_size
的设置,可以参考以下建议:
- 如果能够评估结果集大小,
query_cache_size
可以设置为 20% * 结果集大小; - 如果无法评估结果集大小,
quesry_cache_size
可以设置为 20% *innodb_buffer_pool_size
; - 由于内存总量有限,建议
query_cache_size
和innodb_buffer_pool_size
同步调整,避免实例 OOM;
此外,用户也可以通过查看 Fast Query Cache 提供的状态值信息,查看当前的真实命中率,根据情况动态地对 query_cache_size
进行调整。
总结
Query Cache 的根本目的是用 Memory 换 CPU 和 IO,通过缓存结果集,减少 MySQL 真实执行查询的 CPU 和 IO 消耗。对于读多写少、数据更新不频繁的场景,使用好 Query Cache 可以取得非常好的性能。Fast Query Cache 已在阿里云 RDS MySQL 5.7 最新版本发布,欢迎试用。