知识卡片
"特殊用户"案例:异常基数能让看似合理的索引完全失效
内容
选择性/基数这类经验法则默认数据分布是”正常”的,但真实系统里的数据经常
不是均匀分布,一旦出现极端偏斜的特殊值,按平均情况建立的索引可能在关键
场景下完全不管用。书中给出一个真实案例:某论坛的Message表建了
(groupId, userId)这个多列索引,从EXPLAIN的输出看这个选择顺序是合理的
——问题在于这张表的数据是从另一个系统迁移过来的,迁移过程中把几乎全部
的行(4,142,217行里有4,092,654行,占比接近99%)都挂在了同一个管理员账号
的groupId下。这意味着一旦查询条件命中这个管理员groupId,索引的第一层
过滤几乎不起作用——这个值本身就覆盖了几乎全表,索引形同虚设,而这种
退化在只看”平均”选择性或”表面上看起来正常”的EXPLAIN输出时完全不会
暴露出来。这个案例把
[[多列索引列序选择性优先不如避免随机IO和排序重要]]和
[[前缀索引长度要按最坏情况选择性而非平均值来定]]两条卡片共同指向的道理
落到了一个具体、可复现的场景上:索引设计时依赖的选择性/基数统计,本质
上是对”典型查询会遇到什么样的值”的一个统计假设,而管理员账号、访客/
游客账号、被批量导入或迁移的历史数据、拥有异常多好友/粉丝的用户,这些
“特殊用户”或特殊记录恰恰最容易打破这个假设——它们不是统计上的噪声,
而是会被查询频繁命中的真实业务对象。评估一个索引是否可靠,除了看平均/
典型情况,还应该主动检查数据里是否存在这类已知会产生异常基数的特殊值,
并针对性验证索引在这些值上的表现。
参考来源
- 位置:《高性能MySQL:第3版》第5章"创建高性能的索引"5.3.4节"选择合适
的索引列顺序"(源文件:_epub-src/OEBPS/Text/part0012.xhtml)
- 结论依据:原文明确"这些数据都是从其他系统迁移过来的,且几乎所有的行
都属于一个管理员账号……如果查询中带上了这个管理员用户的groupId,则
MySQL不得不检查数百万行数据",直接说明数据迁移造成的极端基数偏斜
如何使一个EXPLAIN上看起来合理的索引在关键场景下失效。
- 原始内容:这些数据都是从其他系统迁移过来的,且几乎所有的行都属于一个
管理员账号……如果查询中带上了这个管理员用户的groupId,则MySQL不得不
检查数百万行数据。