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

MySQL 默认表达式与生成列的迁移清单

来源:17golang原创

时间:2026-10-31 23:21:53 368浏览 收藏

把应用层的默认赋值迁到 MySQL 时,先不要把 DEFAULT (表达式) 和生成列当成同一种能力:前者只在插入时没有提供该列时补值,后者由其他列计算出结果,不能被应用写入任意覆盖。迁移清单应按“语义、表达式、存储、索引、回退”五项展开。

官方地址:https://dev.mysql.com/doc/refman/8.4/en/

如果字段代表“缺省时的起始值”,用默认表达式;如果字段代表“由基础列决定的派生值”,用生成列。先保留基础列和旧客户端契约,再分阶段改 DDL。

先把默认表达式和生成列分成两条语义链

默认表达式服务于一次写入。下面的 created_at 和 attributes 在 INSERT 省略列名时才取默认值;调用方显式传值,默认值不会替换它。

CREATE TABLE order_item (
  id BIGINT PRIMARY KEY,
  qty INT NOT NULL,
  unit_price DECIMAL(10, 2) NOT NULL,
  -- 插入时未提供时间才使用当前时间
  created_at DATETIME(6) NOT NULL DEFAULT (CURRENT_TIMESTAMP(6)),
  -- JSON 字段的字面量默认值也要写成表达式
  attributes JSON DEFAULT (JSON_OBJECT()),
  -- 总价由基础列计算,禁止应用直接写入
  total_amount DECIMAL(12, 2) AS (qty * unit_price) STORED,
  -- 读取时计算的分类字段不额外占用列存储
  price_band VARCHAR(12) AS (
    CASE
      WHEN unit_price >= 1000 THEN 'premium'
      WHEN unit_price >= 100 THEN 'standard'
      ELSE 'entry'
    END
  ) VIRTUAL
);

MySQL 8.4 中,默认表达式通常用括号包住;TIMESTAMP 和 DATETIME 的 CURRENT_TIMESTAMP 有专门的省略括号规则。VIRTUAL 是生成列的默认存储方式,读取时计算;STORED 在插入或更新时计算并保存,适合需要持久化或建立索引的派生值。

MySQL 默认表达式与 VIRTUAL、STORED 生成列的结构关系说明图
图1:结构说明图,比较 DEFAULT 表达式与生成列的输入、计算和存储边界。

迁移前先清点表达式的硬边界

默认表达式和生成列表达式都支持字面量、运算符以及一部分内置函数,但限制并不相同。生成列要求表达式使用确定性内置函数,不能放子查询、变量、存储函数或可加载函数;NOW()、CURRENT_USER() 等非确定函数不能直接作为生成列依据。

默认表达式也不能使用子查询、参数、变量、存储函数或可加载函数;如果依赖其他列,引用生成列或另一个表达式默认值时要注意列定义顺序,不能形成向前引用。BLOB、TEXT、GEOMETRY 和 JSON 的默认值还必须写成表达式,即使值本身只是一个字面量。

迁移时把以下内容写进设计单:基础列是否允许 NULL、表达式结果是否会发生类型转换、是否依赖 SQL mode、目标存储引擎是否支持表达式默认值、生成列是否需要索引,以及旧客户端是否仍会向派生列发送非 DEFAULT 值。

把迁移拆成可回退的 DDL 依赖

规模化迁移的瓶颈通常不是一条 DDL,而是旧应用、列定义和索引同时改变。建议先把“默认值迁移”和“派生列迁移”拆开,保留每一步的反向脚本。

-- 第一步:只调整新写入的缺省值,不回填历史行
ALTER TABLE order_item
  MODIFY created_at DATETIME(6) NOT NULL
    DEFAULT (CURRENT_TIMESTAMP(6)),
  MODIFY attributes JSON DEFAULT (JSON_OBJECT());

-- 第二步:增加派生列,基础列仍由应用正常写入
ALTER TABLE order_item
  ADD COLUMN total_amount DECIMAL(12, 2)
    AS (qty * unit_price) STORED,
  ADD INDEX idx_order_item_total_amount (total_amount);

-- 回滚脚本要单独保存,且只删除本次新增的对象
ALTER TABLE order_item
  DROP INDEX idx_order_item_total_amount,
  DROP COLUMN total_amount;

第一步不会把旧行的 created_at 改成新值;它影响的是之后省略该列的写入。第二步则会让生成列跟随基础列变化。若选择 VIRTUAL,读取时计算且不占列存储;InnoDB 可以为虚拟生成列建立二级索引。若选择 STORED,要把额外存储、写入计算和索引维护纳入容量与变更窗口评估。

MySQL 默认值列、基础列、生成列索引与回滚脚本的迁移依赖说明图
图2:迁移关系说明图,查看列定义、应用契约、索引和回滚边界的依赖。

上线前用清单确认应用契约没有断裂

  • 确认 INSERT 省略列名、显式传值和写入 DEFAULT 三种情况是否符合业务语义。
  • 确认旧版本客户端不会给生成列发送业务值;生成列被显式写入时,允许的值只有 DEFAULT。
  • 确认基础列的 NULL、精度、字符集和 SQL mode 不会让派生结果发生意外转换。
  • 确认查询是否真的需要索引;STORED 列与索引会同时增加存储和写入维护成本。
  • 确认 SHOW CREATE TABLE 的结果已纳入迁移记录,并准备只撤销本次新增列和索引的回滚脚本。

尤其要留意依赖 DEFAULT() 的生成列表达式:修改基础列默认值可能改变生成列结果,MySQL 可能因此需要重建表。涉及这类依赖时,把 DDL 影响、锁等待和回滚窗口单独排进发布计划。

常见疑问

默认表达式能代替生成列吗? 不能。默认表达式只处理缺省输入,不能持续反映 qty 或 unit_price 的后续变化。

生成列应该优先选 VIRTUAL 还是 STORED? 不需要保存或索引时优先评估 VIRTUAL;需要稳定的物化值或索引时再选择 STORED,并计算额外存储和写入成本。

迁移后历史数据会被默认表达式回填吗? 不会。默认值只在对应写入缺少列值时生效;生成列则按其定义从基础列计算。

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