根本原因是mysql优化器对enum基数估算失真,导致误判低选择性而跳过索引;需通过analyze table刷新统计或改用tinyint/字典表提升可控性。

ENUM字段加了索引为什么还是走全表扫描?
根本原因是 MySQL 优化器对 ENUM 的基数(Cardinality)估算严重失真,尤其当枚举值多、实际分布倾斜(比如 95% 是 'active',其余极少)时,优化器会误判为“低选择性”,直接跳过索引。
验证方式:EXPLAIN SELECT * FROM orders WHERE status = 'shipped'; —— 若 key 列为空、rows 接近表总行数,就是被跳过了。
- 先查统计信息:
SHOW INDEX FROM orders;,看Cardinality是否远低于实际不同值数量(比如定义了 5 个枚举值但显示为 1 或 2) - 强制刷新统计:
ANALYZE TABLE orders;,有时能立刻恢复索引使用 - 若仍无效,说明优化器已彻底放弃该列索引,需考虑替代方案
WHERE条件必须写字符串,不能用数字比较
ENUM 查询语义绑定的是字符串字面量,不是底层整数。写 WHERE status = 2 不仅不走索引,还会导致结果错乱(比如定义顺序是 ('draft','pending','done'),2 对应 'pending',但后续增删枚举值后映射关系就变了)。
- ✅ 正确:
WHERE status = 'done'、WHERE status IN ('draft', 'pending') - ❌ 错误:
WHERE status = 2、WHERE status + 0 > 1(绕过索引,且破坏可维护性) - ⚠️ 注意:
ORDER BY status按定义顺序排,不是字典序;想按业务逻辑排序(如 “draft” → “pending” → “done”),得额外加sort_order字段并建索引
IN 和范围查询效果差异极大
IN 能利用 ENUM 索引,因为等价于多个独立等值查找;但 BETWEEN、、<code>> 基本无效——MySQL 不支持基于枚举定义顺序的范围扫描,内部转成字符串比较,无法走 B+ 树索引。
- 有效:
WHERE status IN ('draft', 'pending')→ 索引命中 - 无效:
WHERE status > 'draft'→ 全表扫描(即使逻辑上只有两个后续值) - ORDER BY 可用但有陷阱:
ORDER BY status走索引避免 filesort,但排序结果严格按定义顺序,不是字母序,也不随业务含义变化
比ENUM更可控的替代方案
当状态需要频繁查询、排序、扩展,或 DBA 需要稳定执行计划时,ENUM 就成了黑盒。直接换成 TINYINT UNSIGNED + 应用层映射,或字典表关联,收益明确。
- 用
TINYINT存状态码:status TINYINT UNSIGNED NOT NULL CHECK (status IN (1,2,3)),建普通索引,范围查询、基数估算、执行计划全部可控 - 加注释说明含义:
-- 1=active, 2=inactive, 3=archived,比ENUM('active','inactive','archived')更易维护 - 大项目建议字典表:
status_types(id, code, name),主表存status_id外键,扩展新状态无需ALTER TABLE,且支持多语言、生效时间等附加属性
真正难的不是选哪种类型,而是意识到:ENUM 的“节省空间”优势,在 SSD 成本极低、内存足够大的今天,几乎可以忽略;而它带来的执行计划不可控、ALTER TABLE 锁表、排序语义模糊等问题,才是线上服务更常踩的坑。











