MySQL 5.7 虚拟列(VIRTUAL)本身不提升性能,必须配合二级索引才能解决函数索引缺失导致的索引失效问题;因其值仅查询时实时计算、不存储,唯有建索引后才固化计算结果并支持快速查找。

MySQL 5.7 中虚拟生成列(VIRTUAL)能直接解决函数索引缺失导致的索引失效问题,但必须配合二级索引才真正生效——单独建虚拟列不加索引,查询性能不会提升。
为什么 VIRTUAL 列必须配二级索引才能加速查询
虚拟列本身不存值,每次读取都实时计算,所以它不自带性能优势。它的价值在于:MySQL 允许在 VIRTUAL 列上创建二级索引(B+Tree),而这个索引是物理存储的、可被 WHERE/ORDER BY 直接命中。
常见错误现象:EXPLAIN 显示 type: ALL 或 key: NULL,即使你已经加了虚拟列。
- 虚拟列本身不可写,不能出现在
INSERT或UPDATE的SET子句中 -
VIRTUAL列不支持主键、唯一约束(除非显式加UNIQUE KEY索引) - InnoDB 是唯一支持
VIRTUAL列二级索引的引擎;MyISAM 不支持 - 索引一旦建立,其值会独立存储——相当于把“计算结果 + 索引”一起固化,避免重复计算
ALTER TABLE ... ADD COLUMN ... AS (...) VIRTUAL 的实操要点
语法看似简单,但表达式限制极严,稍错就会报错 ERROR 3105 (HY000): The value specified for generated column is not allowed.
- 只能引用本表的**非生成列**(即基础字段),不能跨表、不能用子查询、不能用变量或用户函数
- 必须使用确定性函数:允许
CONCAT()、SUBSTRING()、YEAR()、JSON_EXTRACT();禁止NOW()、CURRENT_USER()、RAND() - 类型要显式声明且足够宽:比如
CONCAT(first_name, ' ', last_name)至少要定义为VARCHAR(201)(假设两字段各100字节) - 不写
VIRTUAL也没关系——MySQL 5.7 默认就是VIRTUAL,但建议显式写出,避免和STORED混淆
正确示例:
ALTER TABLE users ADD COLUMN full_name VARCHAR(201) AS (CONCAT(first_name, ' ', last_name)) VIRTUAL;
如何为 JSON 字段中的某个 key 创建可索引的虚拟列
这是 MySQL 5.7 虚拟列最典型的落地场景:绕过 JSON_EXTRACT() 导致的全表扫描。
- 先确保原始列是
JSON类型(不是TEXT),否则->操作符无效 - 虚拟列表达式必须用
->或JSON_EXTRACT(),且路径必须是常量字符串,如doc->"$.status" - 目标字段类型需匹配提取值的实际类型:字符串用
VARCHAR,数字用INT或DECIMAL,布尔值用TINYINT(1) - 加完虚拟列后,立刻执行
CREATE INDEX idx_status ON users(status);才真正启用索引
完整链路:
ALTER TABLE users ADD COLUMN status TINYINT(1) AS (doc->"$.status") VIRTUAL;<br>CREATE INDEX idx_status ON users(status);
之后 SELECT * FROM users WHERE status = 1; 就能走索引了。
容易被忽略的兼容性与维护陷阱
虚拟列不是“设了就一劳永逸”的功能,几个关键点常被跳过:
-
VIRTUAL和STORED无法互相转换:想改类型,只能DROP COLUMN再ADD COLUMN - 修改表达式(比如从
YEAR(create_time)改成DATE_FORMAT(create_time, '%Y-%m'))必须先删列再重建 - 如果原表有触发器,虚拟列在
BEFORE触发器之后才计算,注意逻辑时序 - 备份恢复时,
mysqldump默认包含虚拟列定义,但某些旧版客户端可能解析失败,建议检查SHOW CREATE TABLE输出是否完整
最常踩的坑是:建了虚拟列,忘了建索引;或者建了索引,但表达式用了非确定性函数,导致索引实际不可用——这两步缺一不可,且顺序不能颠倒。


















