MySQL高并发时缓存易击穿雪崩,需通过业务维度key隔离、避免SELECT...FOR UPDATE、用READ COMMITTED隔离级、TTL加随机偏移、连接池与缓存更新时机同步、禁用已废弃Query Cache、合理配置innodb_buffer_pool_size及SQL层兜底(如id>0)来优化。

MySQL 并发高时缓存容易击穿或雪崩
当 SELECT 请求并发突增(比如秒杀、榜单刷新),即使加了 Redis 缓存,仍可能因缓存未命中集中打到 MySQL,引发连接数飙升、Waiting for table metadata lock 或 Lock wait timeout exceeded。这不是缓存没用,而是缓存策略和 MySQL 并发控制没对齐。
- 缓存 key 设计必须包含业务维度隔离(如
"user:profile:),避免全量缓存失效导致穿透" - 读多写少场景下,禁止用
SELECT ... FOR UPDATE包裹纯查询逻辑——它会升级为行锁甚至间隙锁,阻塞其他并发 SELECT - 高并发读建议用
READ COMMITTED隔离级别,而非默认的REPEATABLE READ,减少 MVCC 版本链长度和间隙锁范围 - 缓存过期时间(TTL)别设固定值,应加随机偏移(如
300 + random(60)秒),防止批量失效
MySQL 连接池与缓存更新时机必须同步
应用层用连接池(如 HikariCP、mysql-connector-python 的 pool_size)时,缓存更新若发生在事务提交前,会导致其他连接查到脏数据;若在事务外异步更新,又可能因事务回滚造成缓存与 DB 不一致。
- 强一致性要求:缓存更新放在事务提交后(如 Spring 的
@Transactional+@CacheEvict,确保事务成功才触发删除) - 最终一致性可接受:用 MySQL binlog 解析(如 Canal、Debezium)监听变更,解耦缓存更新,避免应用层事务复杂度
- 禁止在事务中执行耗时缓存操作(如 Redis
SET带大 value 或 pipeline),否则拖慢事务,抬高锁持有时间 - 连接池最大连接数(
max_pool_size)需略大于缓存失效时的并发重建请求数,否则重建线程会排队等连接,放大延迟
Query Cache 已废弃,别再依赖它做并发优化
MySQL 8.0 已彻底移除 query_cache_type 和相关变量。即使你用的是 5.7,开启 query_cache_size > 0 也会在高并发下成为瓶颈:每次写入表都会清空该表所有缓存结果,且缓存锁是全局互斥锁,SELECT 多了反而卡住。
- 替代方案是应用层显式缓存(Redis/Memcached)+ 主动失效,粒度可控
- 如果仍用 MySQL 5.7 且无法改架构,至少把
query_cache_type = 0,关闭它,把资源留给 InnoDB buffer pool -
innodb_buffer_pool_size应设为物理内存的 50%–75%,比任何 Query Cache 都更直接提升并发读性能
缓存穿透时 MySQL 的防护要落在 SQL 层
恶意或异常请求查大量不存在的 id(如 SELECT * FROM user WHERE id = -12345),缓存层没命中,直接压到 MySQL。这时光靠缓存布隆过滤器不够,MySQL 本身也得有兜底。
- 在关键查询上加
WHERE id > 0 AND id 类型的硬约束,避免全表扫描无效值 - 对高频查询字段(如
user_id)建唯一索引,让SELECT ... LIMIT 1能走索引快速返回空结果,而不是等扫描完才确认无数据 - 用
pt-query-digest定期分析慢日志,重点关注Rows_examined高但Rows_sent为 0 的查询——这是穿透典型特征 - 应用层对非法参数(如负 ID、超长字符串)做前置校验,不放行到 DAO 层,比在 MySQL 拦截更省资源
SELECT u.* FROM user u WHERE u.id = ? AND u.id > 0 AND u.status = 'active' LIMIT 1;
真正难的不是配缓存,是判断哪条 SQL 在并发下会抢锁、哪次缓存失效会连环触发重建、以及 MySQL 的 buffer pool 和连接池怎么跟你的缓存 TTL 数值咬合。这些细节不调参、不看慢日志、不模拟压测,只靠“加 Redis”解决不了问题。










