qcache_hits 增长是查询缓存命中的唯一可靠信号;需字节级一致的sql才命中;含函数、变量、注释等即不缓存;mysql 8.0+已移除查询缓存。

直接看 Qcache_hits 是否增长
缓存命中不会报错、不打日志,唯一可靠信号就是 Qcache_hits 状态值是否随查询增加。执行一次 SELECT 后立刻运行:
SHOW GLOBAL STATUS LIKE 'Qcache_hits';
对比前后数值——只增不减,且增幅与你执行的 SELECT 次数一致,才说明真命中了。
- 如果
Qcache_hits没变,但Qcache_inserts增了:说明查询被缓存了,但下次没命中(SQL文本有微小差异) - 如果
Qcache_not_cached增了:查询本身被跳过缓存(含NOW()、RAND()、@var等) - 如果
Qcache_lowmem_prunes飙升:缓存条目刚写入就被挤出,不是没命中,而是存不住
SQL文本必须字节级完全一致
MySQL 查询缓存不解析 SQL,只对原始字符串做哈希。哪怕多一个空格、换一行、大小写不同,都算新查询。
- ✅ 命中:
SELECT id FROM users WHERE id = 1;→ 再执行一模一样的语句 - ❌ 不命中:
select id from users where id=1;(小写+无空格)、SELECT id FROM users WHERE id = 1 -- comment;(加注释)、SELECT id, name FROM users...(字段顺序/数量变) - ORM 尤其危险:Django 的
.filter().order_by()和.order_by().filter()生成的 SQL 字符串不同,缓存键天然不一致
确认缓存没被自动绕过
很多查询根本进不了缓存流程,不是“没命中”,而是压根没资格参与判断。
- 含以下任一成分的
SELECT不会被缓存:NOW()、CURRENT_DATE()、RAND()、USER()、CONNECTION_ID()、LAST_INSERT_ID() - 涉及临时表、用户变量(
@var := 1)、子查询(尤其相关子查询)、FOR UPDATE或LOCK IN SHARE MODE的语句 - 结果集超过
query_cache_limit(默认 1MB),或单条结果超长也会静默跳过 - 若
query_cache_type = DEMAND,还必须显式加SQL_CACHE提示,否则一律不缓存
MySQL 8.0+ 用户请停止检查 Qcache
该版本已彻底移除查询缓存模块,SHOW VARIABLES LIKE 'query_cache%' 和 SHOW STATUS LIKE 'Qcache%' 全部返回空。你看到的“命中率低”,其实是误判——真实瓶颈在 innodb_buffer_pool_read_requests 与 innodb_buffer_pool_reads 的比值上。
复杂点在于:很多人还在用旧监控脚本轮询 Qcache_hits,结果始终为 0 却以为是配置问题,实际是版本不兼容。先跑 SELECT VERSION();,再决定看哪组指标。











