登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL 生成列更新后索引值什么时候刷新

来源:17golang原创

时间:2026-10-06 11:36:55 201浏览 收藏

MySQL 生成列没有单独的“刷新索引”按钮。依赖它的普通列发生变化时,STORED 生成列会在这次行写入中重新计算并保存,相关二级索引项也随同一条写入维护;VIRTUAL 生成列本身不落盘,读取时计算,但建立在它上的 InnoDB 二级索引仍需要在依赖列变化时维护。事务提交和优化器是否选择索引,是另外两个问题。

官方文档:https://dev.mysql.com/doc/refman/8.4/en/create-table-generated-columns.html

要点速览
  • STORED 在插入或更新时计算并保存,VIRTUAL 在读取路径计算;未改变依赖列的 UPDATE 不会凭空产生新的生成值。
  • 生成列有索引时,索引维护属于行写入的一部分,不需要执行 REBUILD、REFRESH 或手工回填。
  • 看到旧值先查事务可见性和实际依赖列,再查索引定义;EXPLAIN 只说明优化器是否选用索引,不证明索引是否刷新。

先分清生成列到底什么时候计算

VIRTUAL 的值不存储在行中,读取时根据表达式计算;没有指定关键字时,生成列默认是 VIRTUAL。STORED 则在行插入或依赖列更新时计算并保存,因此会额外占用行空间。两者都不能像普通列一样写入任意值,显式赋值只允许使用 DEFAULT。

MySQL 生成列 VIRTUAL 与 STORED 的计算时机静态说明图
图1:生成列计算时机说明图。VIRTUAL 在读取路径计算,STORED 在写入路径计算并保存;这是静态关系图,不是数据库截图。
类型值在什么时候计算是否占行存储能否建立索引
VIRTUAL读取时不保存列值InnoDB 支持其二级索引
STORED插入或更新时保存计算结果可以建立索引

用最小表观察值的更新路径

下面同时放置两种生成列,便于观察它们的语义差异。应用只更新 price 或 quantity,不要把生成列放进普通的赋值列表。

CREATE TABLE order_line (
  id BIGINT PRIMARY KEY,
  price DECIMAL(10, 2) NOT NULL,
  quantity INT NOT NULL,
  -- STORED:依赖列写入时计算并保存结果。
  line_total DECIMAL(12, 2)
    GENERATED ALWAYS AS (price * quantity) STORED,
  -- VIRTUAL:读取时根据相同表达式计算,不保存列值。
  line_total_virtual DECIMAL(12, 2)
    GENERATED ALWAYS AS (price * quantity) VIRTUAL,
  -- 两个索引都由 MySQL 在行变更时维护。
  INDEX idx_line_total (line_total),
  INDEX idx_line_total_virtual (line_total_virtual)
);

INSERT INTO order_line (id, price, quantity)
VALUES (1, 12.50, 2);

-- 只改变依赖列;生成列不能在这里写入任意新值。
UPDATE order_line
SET quantity = 3
WHERE id = 1;

-- 提交后重新读取,两列都应按 price * quantity 得到 37.50。
SELECT id, price, quantity, line_total, line_total_virtual
FROM order_line
WHERE id = 1;

这里的“刷新”发生在 UPDATE 维护记录的过程中:quantity 改变后,MySQL 重新评价表达式;STORED 写回新的 line_total,两个索引也同步维护。VIRTUAL 不会留下一个新的物理列值,但它的索引仍然必须反映新的表达式结果。

依赖列改变时索引项怎样跟着维护

要把三个层次分开看。第一层是生成列值有没有按表达式变化;第二层是当前事务或其他事务什么时候能看见这个变化;第三层才是优化器是否觉得索引值得用。前两层正确,并不保证每次 EXPLAIN 都选择该索引。

MySQL 生成列值、二级索引项、事务可见性和优化器选择关系图
图2:生成列索引维护边界图。值维护、事务可见性和优化器选用是三个相邻但不同的判断层,不代表某次真实执行结果。

例如下面的查询用于观察访问路径,而不是手动触发刷新:

-- 让优化器评估生成列索引是否适合这个条件。
EXPLAIN SELECT id, price, quantity
FROM order_line
WHERE line_total = 37.50;

-- 核对表定义,确认索引确实绑定到目标生成列。
SHOW CREATE TABLE order_line;

如果新事务已经提交但执行计划仍走全表扫描,优先检查条件是否与生成列表达式匹配、列类型和隐式转换是否改变了表达式形状、索引是否可见,以及统计信息是否能代表当前数据分布。不要把“没用索引”误判成“索引没刷新”。

看似没有刷新时按四层排查

  1. 先看依赖列。确认 UPDATE 实际改变了表达式引用的列;把列赋回原值时,MySQL 可能识别为没有实际变化。
  2. 再看事务。当前事务能看到自己的写入,其他事务要受隔离级别和提交时机影响。长事务读到旧版本,不等于索引维护失败。
  3. 再看定义。用 SHOW CREATE TABLE 核对生成表达式、STORED/VIRTUAL 类型和索引列,避免应用连到了另一张表或另一套 schema。
  4. 最后看计划。用 EXPLAIN 判断优化器选择;它反映成本估算和表达式匹配,不是索引物理内容的刷新日志。

如果变更的是生成列表达式本身,而不是依赖列的值,就进入 DDL 语义:例如修改 STORED 表达式可能需要重建数据;这和普通 UPDATE 后的索引项维护不是同一条路径。生产变更前应先查看对应版本的 ALTER TABLE 说明和执行计划。

检查清单:不要把三个问题混在一起

现象先验证什么正确判断
SELECT 看到新生成值依赖列、提交状态值计算路径正常
其他事务仍看到旧值事务隔离与快照可能是可见性,不是索引问题
EXPLAIN 没选生成列索引条件表达式、统计信息、索引可见性可能是优化器成本决策
修改了生成列表达式ALTER TABLE 执行方式属于 DDL 变更,可能涉及重建

因此,标题里的“什么时候刷新”可以落成一句可执行的判断:普通依赖列改变时,生成列和它的索引在该行写入过程中维护;STORED 的列值在写入时保存,VIRTUAL 的列值在读取时计算;提交后其他事务何时看见,以及查询是否使用索引,分别按事务和优化器规则判断。

相关问题

可以直接 UPDATE 生成列吗?

不能写入任意计算结果。MySQL 对生成列的显式赋值只允许使用 DEFAULT,实际业务应更新表达式依赖的普通列。

VIRTUAL 生成列没有存储值,为什么还能建索引?

列值本身不作为普通行字段保存,但 InnoDB 可以维护基于该表达式结果的二级索引;依赖列变化时,索引项仍要同步调整。

索引没有被 EXPLAIN 选中,是否说明刷新失败?

不说明。EXPLAIN 展示的是优化器对当前条件、统计信息和成本的选择。先用 SELECT 验证值,再用 SHOW CREATE TABLE 核对定义,最后分析访问路径。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>