文章目录
- [一、什么是生成列(Generated Column)](#一、什么是生成列(Generated Column))
- [二、STORED 与普通字段有什么区别?](#二、STORED 与普通字段有什么区别?)
- [三、为什么 STORED 列可以让 SQL 飞起来?](#三、为什么 STORED 列可以让 SQL 飞起来?)
- 四、一个医院真实业务案例:统计工作日到访人数
- [五、使用 STORED,企业级写法来了](#五、使用 STORED,企业级写法来了)
- [六、为什么企业更喜欢 STORED 而不是 VIRTUAL?](#六、为什么企业更喜欢 STORED 而不是 VIRTUAL?)
- [七、STORED 生成列 + dim_date = 双剑合璧最强方案](#七、STORED 生成列 + dim_date = 双剑合璧最强方案)
- 八、生产注意事项
- [九、总结:一句话记住 STORED](#九、总结:一句话记住 STORED)
在 MySQL 8 中,如果你经常写带函数判断的 SQL,例如:
sql
WHERE WEEKDAY(creatime) < 5
你会发现:
- 索引无法使用
- 执行计划
type = ALL - 大表查询慢得像蜗牛
常见的数据计算,比如"是否工作日、是否有效"、"金额是否超过阈值"、"是否逾期"等,都容易写成函数形式,导致索引无法命中。
在高并发、大数据量的场景下,这种写法会拖垮整个系统
解决办法是什么?
👉 MySQL 生成列(Generated Column)+ STORED(存储列) + 索引
一、什么是生成列(Generated Column)
MySQL 的生成列有两种:
| 类型 | 特点 |
|---|---|
| VIRTUAL 虚拟列 | 不存储,查询时现算 |
| STORED 存储列 | 算完真实写入磁盘,可建索引 |
生成列的语法:
sql
column_name data_type
GENERATED ALWAYS AS (表达式)
[VIRTUAL | STORED]
例如,根据 creatime 自动计算是否工作日:
sql
is_workday TINYINT
GENERATED ALWAYS AS (
CASE WHEN WEEKDAY(creatime) < 5 THEN 1 ELSE 0 END
) STORED
二、STORED 与普通字段有什么区别?
很多人不清楚为什么"用 STORED 很香",下面用一个表格秒懂👇
| 对比项 | 普通字段 | STORED 生成列 |
|---|---|---|
| 值由谁计算? | 开发者自己写入 | MySQL 根据表达式自动算 |
| 更新时是否要维护? | 要自己维护 | creatime 改,自动重算 |
| 能否防止脏数据? | 容易写错、漏改 | 保证永远正确 |
| 能否建索引? | 可以 | 可以(而且非常常用) |
| 查询时需不需要重新计算? | 不需要 | 不需要 |
| 写入性能 | 一般 | 插入时计算一次 |
| 典型场景 | 普通字段 | 业务派生字段(是否周末、是否逾期、金额区间等) |
一句话总结:
STORED = 自动计算的普通字段,可建索引,是 SQL 优化神器。
三、为什么 STORED 列可以让 SQL 飞起来?
来看经典错误写法:
sql
WHERE WEEKDAY(creatime) < 5
你对 creatime 做了函数:
- creatime 索引用不了
- 强制全表扫
- 大数据量直接炸
而 STORED 生成列写法:
sql
WHERE is_workday = 1
它是普通字段:
- 可以建索引
- 非常高效
- 查询极快
MySQL 查询优化器最喜欢:
字段 = 常量
字段 BETWEEN 区间
字段 IN (...)
生成列完美契合这一点。
四、一个医院真实业务案例:统计工作日到访人数
医院表 t_visit:
sql
CREATE TABLE t_visit (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
hospital_id INT,
creatime DATETIME,
visit_num INT
);
需求:
统计各医院在工作日(周一到周五)的就诊人数
错误写法:索引完全失效!
sql
SELECT SUM(visit_num)
FROM t_visit
WHERE WEEKDAY(creatime) < 5;
解释:
- Creatime 上套函数 → 索引失效
- 查询 100W 行 → 全表扫描
- 业务卡死
五、使用 STORED,企业级写法来了
1)添加生成列
sql
ALTER TABLE t_visit
ADD COLUMN is_workday TINYINT
GENERATED ALWAYS AS (
CASE WHEN WEEKDAY(creatime) < 5 THEN 1 ELSE 0 END
) STORED,
ADD INDEX idx_visit_workday (is_workday, creatime);
2)正确查询写法
sql
SELECT
hospital_id,
SUM(visit_num)
FROM t_visit
WHERE
is_workday = 1
AND creatime BETWEEN '2025-01-01' AND '2025-02-01'
GROUP BY hospital_id;
EXPLAIN 显示:
type = rangekey = idx_visit_workday- 几万行 → 几千行
- 性能提升 5~30 倍
六、为什么企业更喜欢 STORED 而不是 VIRTUAL?
| 维度 | VIRTUAL | STORED |
|---|---|---|
| 存储方式 | 不落盘 | 落盘 |
| 查询成本 | 每查都计算 | 不需要计算 |
| 能否建 index | 老版本不支持、多版本有限制 | 全版本支持,生产常用 |
| 性能 | 适合小数据 | 适合大数据、OLTP、高并发 |
大量业务都在用:
- 是否工作日
- 是否节假日
- 是否逾期
- 是否有效
- 金额区间分类(如大单、中单、小单)
- 年龄段分类
- 设备状态派生字段
只要是某列可以推导出来的值,且要做过滤、排序、聚合,80% 的情况下会用 STORED。
七、STORED 生成列 + dim_date = 双剑合璧最强方案
在 BI / 数仓中常用维表:
sql
CREATE TABLE dim_date (
date_key DATE PRIMARY KEY,
weekday TINYINT,
is_workday TINYINT,
is_holiday TINYINT,
holiday_name VARCHAR(20)
);
事实表:
sql
ALTER TABLE t_visit
ADD visit_date DATE GENERATED ALWAYS AS (DATE(creatime)) STORED,
ADD INDEX (visit_date);
查询:
sql
SELECT
v.hospital_id,
SUM(v.visit_num)
FROM t_visit v
JOIN dim_date d ON v.visit_date = d.date_key
WHERE
d.is_workday = 1
GROUP BY v.hospital_id;
优势:
- 超高性能
- 法定节假日、调休随便改
- 报表、看板、数据集市都复用 dim_date
- 企业统一口径
八、生产注意事项
- 生成列不能手工 INSERT / UPDATE
- 表插入非常频繁时,STORED 会多一次计算成本(但一般可以接受)
- 表过大时,修改表结构添加 STORED 列要注意线上压力
- 建立索引时一定要注意前导列(选择性越高越好)
- 如果你的计算很复杂,可以考虑 STORED + 函数表达式预处理
九、总结:一句话记住 STORED
STORED 生成列,是 MySQL 自动计算、自动维护、可建索引的派生字段。
它让复杂 SQL 拆分成"插入时算一次,查询时用高速索引",
是 OLTP 性能优化最常用、最实用也最容易被忽略的武器。