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-2 字节 vs VARCHAR 的每个字符 1-4 字节
- 可读性好:看到的是有意义的字符串,不是数字
- 数据完整性:只允许预设值,非法插入自动报错
- 排序语义:按定义顺序排序(不是字母序!)
缺点
- 扩展需要 DDL:增加枚举值必须
ALTER TABLE,对大表成本高 - 排序陷阱:很多人以为 ENUM 按字母序排序,实际上按定义顺序
- 与其他数据库不兼容:不是 SQL 标准类型,迁移麻烦
- 排序和 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 的场景
- 值集绝对固定且极少变化:如性别(’male’, ‘female’)、方向(’N’,’S’,’E’,’W’)
- 值集非常小:2-10 个选项
- 值不会有业务含义变更:如状态码
- 数据量级大:利用 ENUM 的存储优势和索引效率
不适合使用 ENUM 的场景
- 值集可能频繁变化:如产品分类、标签
- 值集超过几十个:ENUM 的设计不适合大量枚举值
- 需要跨数据库迁移:PostgreSQL 等使用不同的枚举语法
- 应用层需要统一管理枚举:如 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 或字典表,因为扩展性更好,也方便做国际化。


暂无评论内容