ENUM 枚举类型使用建议

ENUM 枚举类型使用建议

什么是 ENUM

ENUM(枚举)是 MySQL 的一种字符串类型,但内部通过整数存储,用于表示一组固定的值:

CREATE TABLE users (
    status ENUM('active', 'inactive', 'banned', 'pending')
);

ALTER TABLE orders ADD COLUMN status ENUM('pending','paid','shipped','delivered','cancelled');

ENUM 的存储本质

ENUM 内部存储的是整数索引,从 1 开始:

status ENUM('active', 'inactive', 'banned', 'pending')
-- 'active'   → 1
-- 'inactive' → 2
-- 'banned'   → 3
-- 'pending'  → 4

存储时只占 1-2 字节(取决于元素数量,<255 个用 1 字节),而存等价值的 VARCHAR 可能占更多空间。

ENUM 的优缺点

优点

  1. 存储紧凑:1-2 字节 vs VARCHAR 的每个字符 1-4 字节
  2. 可读性好:看到的是有意义的字符串,不是数字
  3. 数据完整性:只允许预设值,非法插入自动报错
  4. 排序语义:按定义顺序排序(不是字母序!)

缺点

  1. 扩展需要 DDL:增加枚举值必须 ALTER TABLE,对大表成本高
  2. 排序陷阱:很多人以为 ENUM 按字母序排序,实际上按定义顺序
  3. 与其他数据库不兼容:不是 SQL 标准类型,迁移麻烦
  4. 排序和 GROUP BY 的坑:空字符串(”)排在第一位,NULL 排最后

ENUM 的排序陷阱

这是面试最高频的 ENUM 考点:

CREATE TABLE t (priority ENUM('high', 'medium', 'low'));

INSERT INTO t VALUES ('high'), ('medium'), ('low');

SELECT * FROM t ORDER BY priority;
-- 结果顺序:high, medium, low(按定义顺序,不是字母序!)

如果期望字母序排序,需要用:

SELECT * FROM t ORDER BY CAST(priority AS CHAR);

ENUM 的值类型

ENUM 值可以是字符串,也可以是数字(但强烈不建议混用):

-- 可以,但容易混淆
CREATE TABLE t (level ENUM('1','2','3'));

-- 插入 '1' 和 1 的行为不同:
-- '1' → 匹配枚举值 '1'(索引 1)
-- 1 → 也是索引 1,但意义不同

为了可读性,建议只用有意义的字符串作为枚举值。

最佳实践

适合使用 ENUM 的场景

  1. 值集绝对固定且极少变化:如性别(’male’, ‘female’)、方向(’N’,’S’,’E’,’W’)
  2. 值集非常小:2-10 个选项
  3. 值不会有业务含义变更:如状态码
  4. 数据量级大:利用 ENUM 的存储优势和索引效率

不适合使用 ENUM 的场景

  1. 值集可能频繁变化:如产品分类、标签
  2. 值集超过几十个:ENUM 的设计不适合大量枚举值
  3. 需要跨数据库迁移:PostgreSQL 等使用不同的枚举语法
  4. 应用层需要统一管理枚举:如 Java 枚举类与数据库 ENUM 的映射维护

ENUM 的替代方案

方案 1:TINYINT + 注释文档

CREATE TABLE orders (
    status TINYINT COMMENT '1=pending, 2=paid, 3=shipped, 4=delivered, 5=cancelled'
);

优点:扩展只需修改注释,不锁表;排序和运算快。
缺点:SQL 语句中直接看数字不直观,需要业务代码映射。

方案 2:关联字典表

CREATE TABLE order_status (
    id TINYINT PRIMARY KEY,
    name VARCHAR(20) UNIQUE
);
INSERT INTO order_status VALUES 
    (1, 'pending'), (2, 'paid'), (3, 'shipped'), 
    (4, 'delivered'), (5, 'cancelled');

CREATE TABLE orders (
    status_id TINYINT REFERENCES order_status(id)
);

优点:标准化设计,扩展方便,可加更多属性。
缺点:每次查询需要 JOIN。

方案 3:VARCHAR + CHECK

MySQL 8.0.16+ 支持 CHECK 约束:

CREATE TABLE orders (
    status VARCHAR(20) CHECK (status IN ('pending','paid','shipped','delivered','cancelled'))
);

优点:SQL 标准兼容,其他数据库可直接迁移。
缺点:VARCHAR 占用空间比 ENUM 大。

选择对比表

特性 ENUM TINYINT 字典表 VARCHAR + CHECK
存储空间 1-2 字节 1 字节 1+字节 按长度
可读性 ✅ 好 ❌ 数字 ✅ JOIN 后好 ✅ 好
扩展性 ❌ 需 DDL ✅ 改注释即可 ✅ 插入即可 ✅ 改约束
排序 按定义顺序 按数字 按 ID 按字母序
兼容性 ❌ MySQL 特有 ✅ 通用 ✅ 通用 ✅ 标准
索引效率 ✅ 高 ✅ 高 ⭐ 中等

面试常问题

Q:ENUM 排序时 ‘c’ 和 ‘a’ 谁在前面?
A:与字母序无关,取决于定义顺序。如果定义是 ENUM('a','b','c'),则 ‘a’ 排第一。

Q:ENUM 列怎么加一个新值?
A:使用 ALTER TABLE t MODIFY COLUMN col ENUM('a','b','c','d')。注意:ALTER TABLE 对大表会重建表,业务高峰期执行有风险。

Q:ENUM 和 SET 有什么区别?
A:ENUM 单选,SET 多选(可以组合多个值);SET 内部用位图存储,每个位代表一个选项。

Q:项目里应该用 ENUM 吗?
A:对于不会变化的小型枚举(如性别、订单状态几个值)可以安全使用。但业界更趋向于用 TINYINT 或字典表,因为扩展性更好,也方便做国际化。

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容