虚拟列是基于其他列动态计算的逻辑列,不占磁盘空间,查询时实时求值;STORED列则在写入时计算并物理存储,支持索引、外键等。

虚拟列(Generated Column)不是真实存储的数据字段,而是基于表中其他列通过表达式动态计算得出的逻辑列。它的核心原理是“定义即计算”,不占磁盘空间,查询时实时求值;而派生列若设为 STORED 类型,则属于物理存储的派生列,在写入或更新时完成计算并落盘。
虚拟列怎么工作
当你定义一个 VIRTUAL 生成列,比如:
CREATE TABLE logs (id INT, content TEXT, created_at DATETIME, date_only DATE GENERATED ALWAYS AS (DATE(created_at)) VIRTUAL);
MySQL 并不会在磁盘上为 date_only 分配空间。每次执行 SELECT date_only FROM logs,引擎都会现场调用 DATE(created_at) 计算该行结果。这意味着:
- 值永远与源列保持强一致性,无需额外维护
- 不增加 B+ 树页大小或备份体积
- 无法直接对虚拟列做 INSERT 或 UPDATE(会报错)
- 支持在 WHERE、ORDER BY、GROUP BY 中使用,但不能作为主键或外键
STORED 列才是真正的派生存储
STORED(也称 PERSISTENT)生成列才具备“派生列”的完整存储语义。它在 INSERT/UPDATE 时触发计算,并把结果像普通列一样写入聚簇索引或二级索引页中。例如:
ALTER TABLE orders ADD COLUMN amount_with_tax DECIMAL(12,2) GENERATED ALWAYS AS (amount * 1.08) STORED;
这个值被真正存下来,因此可以:
- 对其创建索引(如 INDEX idx_tax (amount_with_tax)),加速范围查询
- 参与外键约束(MySQL 8.0.23+ 支持 STORED 列作外键)
- 被 InnoDB 的 MVCC 快照机制正常读取和版本管理
- 占用实际行存储空间,影响单页容纳行数
为什么不能混用虚拟列之间引用
MySQL 明确禁止虚拟列引用另一个虚拟列,例如:
-- ❌ 错误:不能在 virtual_col2 的表达式中引用 virtual_col1
col1 INT,
virtual_col1 VARCHAR(20) GENERATED ALWAYS AS (UPPER(LEFT(col1, 3))) VIRTUAL,
virtual_col2 VARCHAR(20) GENERATED ALWAYS AS (CONCAT(virtual_col1, '_suffix')) VIRTUAL
这是因为虚拟列无存储实体,其值仅在 SELECT 执行阶段存在,DDL 解析时无法解析依赖链。但允许嵌套函数调用,如 UPPER(SUBSTRING(email, 1, LOCATE('@', email)-1)) 是合法的。
虚拟列如何提升查询性能
表面看虚拟列每次都要算,似乎拖慢查询,但它真正的价值在于“让表达式可索引”。例如:
- 原始查询 WHERE LOWER(email) = 'admin@test.com' 无法使用 email 上的普通索引
- 建虚拟列 email_lower VARCHAR(100) GENERATED ALWAYS AS (LOWER(email)) STORED,再加索引,就能走索引查找
- 同理,JSON_EXTRACT(data, '$.status')、DATE(created_time) 等高频过滤表达式,转为 STORED 虚拟列 + 索引后,性能跃升明显










