首页/数据/SQL 速查/生成列(虚拟与存储)
变更与事务52 条

生成列(虚拟与存储)

由其他列计算得出:虚拟列即时计算不占存储,存储列落盘、可建索引;常用于把表达式固化成列。

SQL 速查数据更新于 2026-10-13

生成列怎么用

生成列的值由其他列计算得出,写入时不用也不该手动提供。MySQL 有虚拟列与存储列两种:虚拟列不落盘、查询时计算,可以建二级索引;存储列把结果落盘,读取更快但占空间。PostgreSQL 目前只支持存储形式。常见用法是把表达式固化成列,再配索引服务固定的查询模式。

基本信息

MySQL 虚拟列默认 VIRTUAL,不落盘,查询时计算,可建二级索引
MySQL 存储列STORED,结果落盘,可建索引,占空间
PostgreSQL目前仅支持 STORED,表达式必须是不可变函数
写入方式只能写被依赖的源列,直接写生成列会报错
典型用途把表达式固化成列并加索引

典型使用场景

  • 对 JSON 字段派生出的值建索引
  • 把订单金额换算成含税金额固化下来
  • 从时间戳派生日、年等维度列
  • 把复杂表达式固化成列以便索引命中
  • 让报表固定口径,不必每次重算

MySQL 写法

MySQL 写法
-- 存储列:结果落盘,可建索引
ALTER TABLE orders
  ADD COLUMN total_with_tax DECIMAL(12,2)
  GENERATED ALWAYS AS (total * 1.13) STORED;

-- 虚拟列加二级索引:列本身不落盘,索引里存值
ALTER TABLE orders
  ADD COLUMN order_year INT GENERATED ALWAYS AS (YEAR(created_at)) VIRTUAL,
  ADD INDEX idx_year (order_year);

PostgreSQL 写法

PostgreSQL 写法
-- 只支持 STORED 形式,表达式须为不可变函数
ALTER TABLE orders
  ADD COLUMN total_with_tax numeric(12,2)
  GENERATED ALWAYS AS (total * 1.13) STORED;

-- 查看哪些列是生成列
SELECT attname, attgenerated
FROM pg_attribute
WHERE attrelid = 'orders'::regclass AND attgenerated <> '';

两种生成列对照

对比项虚拟列(MySQL)存储列
是否落盘不落盘,查询时计算落盘,占空间
读取成本每次查询都要计算直接读,成本低
能否建索引可以(索引存值)可以
表达式变更立即生效需重建才能反映新公式
适用场景计算轻、想省空间计算重、读多写少
改表达式不会自动重算已有数据:存储列需要重建表或批量更新才能换成新公式的结果,改之前先评估数据量与窗口期。
表达式必须是确定性的:依赖当前时间或随机数的表达式在建列时就会被拒绝,需要这种取值就在写入时算好存进普通列。

易错点

  • 表达式变更后旧值不自动重算:存储列需要重建或批量更新才能反映新公式
  • 试图直接 INSERT 或 UPDATE 生成列:会直接报错,只能写它依赖的源列
  • 在 PostgreSQL 里期待虚拟列:目前只支持存储形式,占用空间要提前评估
  • 表达式里用非确定性函数:建列时就会被拒绝,需改为写入时计算
  • 以为生成列自动带索引:索引仍要显式创建,尤其是用派生值做查询条件的场景

数据来源与参考

  • MySQL 8.0 Reference Manual · Generated Columns
  • PostgreSQL Documentation · Generated Columns
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌