mysql 8.0 中生成列必须显式声明 virtual 或 stored,缺一不可;virtual 列实时计算、不落盘,stored 列持久化存储、可建索引,二者均不可直接插入值且表达式须确定性。

GENERATED 列不是“语法糖”,而是需要你明确选择 VIRTUAL 或 STORED 的真实列类型。不加类型声明会直接报错。
生成列必须显式指定 VIRTUAL 或 STORED
MySQL 8.0 不允许省略生成列的存储类型。写 GENERATED ALWAYS AS (expr) 而不跟 VIRTUAL 或 STORED,会报错 ERROR 1901 (HY000): Function or expression '<expr>' cannot be used in a generated column</expr>(常见于表达式含不确定函数,如 NOW())或更直接的语法错误。
-
VIRTUAL:值不落盘,每次SELECT时实时计算,节省空间但增加 CPU 开销 -
STORED:值在INSERT/UPDATE时计算并持久化,读取快,但占用磁盘且受NOT NULL约束限制(比如表达式返回NULL且列定义为NOT NULL,会插入失败) - 表达式里不能用子查询、存储函数、用户变量、
UUID()、RAND()、NOW()等非确定性函数
生成列能用在哪些地方?
它最常被用来替代应用层拼接逻辑,比如 URL 构建、状态推导、标准化格式输出。但要注意:生成列本身不能被 INSERT 或 UPDATE 直接赋值(除非是 STORED 且设了默认值),也不能作为 PRIMARY KEY(即使表达式结果唯一)。
- 可建索引:MySQL 8.0.13+ 支持在
STORED列上建普通索引;VIRTUAL列也能建索引,但仅限于等值查询(=),且索引实际存储的是计算结果 - 可用于
WHERE、ORDER BY、GROUP BY,但VIRTUAL列在WHERE中可能无法走索引(取决于优化器是否下推) - 不能用于外键引用目标列(即不能做
REFERENCES的那一方)
加生成列时 ALGORITHM=INSTANT 是否生效?
可以,但前提是表满足所有 INSTANT 条件——且加生成列属于“元数据变更”,天然适配该算法。不过有两点容易忽略:
- 即使用了
ALGORITHM=INSTANT,AFTER col_name在 MySQL 8.0.28 及以下版本会被无视,新列总追加到末尾;8.0.29+ 才真正支持位置控制,但仅对空表或全行已含 instant 元信息的表生效 -
STORED列在首次ALTER时会触发全表扫描计算初始值(哪怕表很大),这个过程不是 instant 的;只有后续的INSERT/UPDATE才享受 instant 行格式优势 - 如果表已有
FULLTEXT索引,或ROW_FORMAT=COMPRESSED,ALGORITHM=INSTANT会静默退化为INPLACE,导致加列变慢甚至锁表
生成列和普通列在 INSERT 时行为差异
生成列不允许出现在 INSERT ... VALUES 的字段列表中(会报 ERROR 3105 (HY000)),但可以通过 DEFAULT 或显式 NULL(如果允许)绕过。关键是:它的值完全由表达式决定,不受客户端输入干扰。
- 对
VIRTUAL列,INSERT INTO t(a, b) VALUES(1, 2)就够了,c(生成列)自动算出 - 对
STORED列,如果表达式依赖的字段为NULL导致结果为NULL,而该生成列又定义了NOT NULL,则整条INSERT失败 -
INSERT ... SELECT时,若SELECT列表里包含生成列名,MySQL 会拒绝执行(哪怕你 SELECT 的是常量),因为生成列不可写
真正麻烦的不是怎么写 GENERATED ALWAYS AS,而是想清楚:这个值是不是真的该由数据库“强制统一生成”——一旦上线,改表达式就得重建列(STORED)或重定义(VIRTUAL),而且老数据不会自动重算。别为了省几行应用代码,把业务逻辑耦合进表结构里。











