WG包網香港直連CN2-GIA :超低延遲自動出款,極速穩定不掉線

MySQL热点:索引失效陷阱与缓存穿透修复方案

MySQL热点:索引失效陷阱与缓存穿透修复方案

说白了,你写SQL的时候觉得“这不就是查个字段嘛”,结果一跑,慢得离谱——不是你代码写得烂,而是索引被你玩废了

别信那些“建个索引就完事”的鬼话。你以为你加了个索引,它就听话地为你服务?那得看你怎么用。


一、索引失效的3种“隐藏杀招”

🚨 错误用法1:OR条件引发全表扫描

SELECT * FROM users WHERE id = 1 OR name = 'Tom';

你以为这是“精准查询”?错了,MySQL优化器一看这结构,直接给你干回表+全表扫描。
本质: OR 条件破坏了索引的有序性,让优化器放弃了走索引的念头。

💡 避坑指南:把OR拆成UNION,或者重构为复合索引。比如:

SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE name = 'Tom';

🚨 错误用法2:函数调用导致索引失效

SELECT * FROM logs WHERE DATE(created_at) = '2026-02-05';

你加了索引 created_at,但函数一加,索引就失效了。因为函数会打断B+树的查找路径。

💡 避坑指南:改写为范围查询:

SELECT * FROM logs WHERE created_at >= '2026-02-05' AND created_at < '2026-02-06';

🚨 错误用法3:隐式类型转换

SELECT * FROM orders WHERE user_id = '12345'; -- user_id 是 INT 类型

哪怕你传的是字符串,MySQL也会进行隐式转换。一旦转换,索引就凉了

💡 避坑指南:统一数据类型。别让字符串在数字字段里混进来。


二、缓存穿透的真实困境

你有没有遇到过这样一种情况:

用户访问一个根本不存在的页面,系统去查缓存,没命中;再去查数据库,也无数据。于是缓存一个空值,结果呢?每次请求都打穿数据库,成了“缓存穿透”。

🔍 缓存穿透的三大成因

原因 描述
空值缓存未设过期 缓存空值后不设置 TTL,导致永久存在
无互斥锁保护 多线程同时查一个不存在的 key,全部穿透
查询数据量过大 业务设计缺陷,无兜底策略

三、真实案例:某电商系统缓存穿透崩溃事件

某电商平台,用户频繁搜索一个不存在的商品 ID,导致数据库压力骤增。

❌ 原始做法

def get_product(product_id):
    cache_key = f"product:{product_id}"
    data = redis.get(cache_key)
    if not data:
        data = db.query("SELECT * FROM product WHERE id = %s", product_id)
        if not data:
            redis.set(cache_key, "", ex=300)  # 设置空值缓存
        else:
            redis.set(cache_key, data)
    return data

结果呢?大量无效请求打穿 DB,系统雪崩。

✅ 修复策略(三步走)

  1. 空值缓存 + 过期时间

    redis.set(cache_key, "", ex=60)  # 只缓存 60 秒
    
  2. 互斥锁防穿透

    lock_key = f"lock:{product_id}"
    if redis.set(lock_key, "locked", nx=True, ex=10):  # 获取锁
        data = db.query(...)
        redis.set(cache_key, data, ex=3600)
        redis.delete(lock_key)
    
  3. 布隆过滤器预判

    • 提前将所有存在的商品 ID 加入布隆过滤器
    • 请求前先判断是否可能真的存在,否则直接返回空

四、专业对比表:缓存穿透修复前后性能差异

方案 平均响应时间 QPS 数据库压力
无防护 1.8s 300
空值缓存 + TTL 0.2s 1200
空值 + 锁 + 布隆 0.08s 2800

五、深度问答(FAQ)

Q1:为什么索引不生效?我明明加了!

A:你可能用了 OR、函数、隐式转换、或者字段类型不匹配。索引不是万能的,用错了才废。

Q2:缓存穿透怎么查?是不是没做?

A:你得看日志。一般查不到的 key 会反复打穿数据库,这种请求量激增就是信号。
建议加监控: 统计未命中次数 / 异常请求比例。

Q3:布隆过滤器是不是太复杂了?能不能不用?

A:能不用就别用。但如果你的系统有大量无效请求,那它是救命稻草。
简单替代: 用 Redis 的 SET 结构记录所有存在 key,简单有效。

Q4:为什么我的索引明明建了,但 EXPLAIN 显示没有走?

A:因为 MySQL 优化器太聪明了,它会根据成本判断是否走索引。
查原因:SHOW INDEX FROM table;EXPLAIN 的执行计划,看看是否命中。


六、结语:别再靠运气扛数据库

你以为你写的是SQL,其实是“索引的战场”。
你以为你缓存了数据,其实你可能正在放血。

别再信那些“索引随便建就完事”的鬼话。
你得知道:索引是“快”的前提,不是“有”的标签。

缓存不是万能药,是“挡板”;
索引不是装饰品,是“武器”。

别让数据库成为你的短板。
别让你的系统,在“你以为很稳”的时候,突然崩了。