当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL 生成列为什么不能引用不确定函数

MySQL 生成列为什么不能引用不确定函数

来源:17golang原创 2026-09-12 11:22:15 0浏览 收藏

我第一次给业务表加生成列时,想做一件看起来很合理的事:把用户年龄算出来存进表里。生日已经有了,CURDATE() 也能拿到今天,按理说写成一列就完事。结果 DDL 直接被 MySQL 拒绝了。

后来我才意识到,生成列不是“把一段查询语句藏进表结构”,它更像一个必须稳定兑现的约定:只要基础列没变,生成结果就应该能被重复计算出来。函数如果会随着时间、连接或环境变化,生成列和索引就没有可靠的依据。

我的判断
  • 生成列适合表达“由同一行基础列稳定推导出来”的值。
  • NOW()CURDATE()CONNECTION_ID()CURRENT_USER() 这类结果会变化的函数不能直接放进去。
  • 年龄、当前状态这类随时间变化的值放查询层;JSON 字段提取、字符串规范化等稳定表达式才适合做生成列。

我踩到的第一个坑:今天的年龄不是列值

下面这段写法很符合人的直觉,但它把“当前时间”混进了列定义:

CREATE TABLE user_profile (
    id BIGINT PRIMARY KEY,
    birthday DATE NOT NULL,
    age INT AS (TIMESTAMPDIFF(YEAR, birthday, CURDATE())) STORED
); -- 中文说明:年龄随今天变化,不适合固化成生成列

MySQL 8.4 的生成列规则要求表达式使用字面量、运算符和确定性内置函数。确定性可以简单理解为:给定相同的表中数据,多次计算应得到同一个结果,并且不能依赖当前连接用户。NOW()CONNECTION_ID()CURRENT_USER() 都属于官方文档列出的不确定函数,CURDATE() 也有同样的问题。

这不是 MySQL 故意为难人。假如今天算出 28 岁,明天同一行基础列没变却应该变成 29 岁,那么 STORED 值、虚拟列读取结果和基于它建立的索引就不再是同一件事。对我来说,遇到这类需求时最先要问的不是“怎么绕过 DDL”,而是“这个值到底是不是数据本身”。

MySQL 生成列中确定性输入与不确定函数的边界关系
图1:生成列要求基础列和表达式形成稳定关系,不确定函数会把当前时间或连接环境带进结果。

能放进去的,通常是同一行数据的稳定变换

我把生成列表达式分成两类看,判断会快很多。第一类是“同一行换一种表示”:把邮箱转小写、从 JSON 中取出固定字段、把两个姓名列拼成展示名。第二类是“结果会随外部状态变”:当前时间、随机数、连接身份、查询其他表。

CREATE TABLE customer_profile (
    id BIGINT PRIMARY KEY,
    email VARCHAR(200) NOT NULL,
    profile JSON NOT NULL,
    email_key VARCHAR(200) AS (LOWER(email)) STORED,
    level_key VARCHAR(32) AS (
        JSON_UNQUOTE(JSON_EXTRACT(profile, '$.level'))
    ) VIRTUAL,
    KEY idx_email_key (email_key)
); -- 中文说明:这些结果只依赖当前行,可作为检索或统一读取入口

这里的重点不是把所有清洗逻辑都塞进表里,而是让重复出现、规则稳定的表达式有一个明确名字。MySQL 文档还特别提醒,生成列表达式不能使用子查询、变量、存储函数和可加载函数;引用其他生成列时,只能引用定义在前面的列。

还有一个容易混淆的地方:函数名字听起来“纯”,不等于它在当前场景下一定满足生成列规则。遇到不熟悉的函数,我会先查对应版本的官方手册,再用一张临时表验证,而不是凭经验把 DETERMINISTIC 写进某个存储函数定义里就认为安全。

年龄、JSON 和索引,我现在会这样分开处理

年龄是动态值,查询时计算更诚实:

SELECT
    id,
    TIMESTAMPDIFF(YEAR, birthday, CURDATE()) AS age
FROM user_profile
WHERE id = 1001; -- 中文说明:每次读取都按当天计算,不把年龄伪装成稳定列值

JSON 中的会员等级就不一样了。只要 profile 这一行没变,提取路径的结果就稳定,可以用虚拟生成列统一查询;如果这个字段经常作为筛选条件,再考虑存储或建立索引。这样既没有把“今天”写进表结构,也避免每个查询都重复写一长段 JSON 表达式。

我会把选择简单化:

场景更合适的做法原因
随当前日期变化的年龄查询时计算结果不是稳定列值
从本行 JSON 取固定字段VIRTUAL 生成列统一表达式,按需计算
稳定值且筛选频繁STORED 或生成列索引让检索路径更明确
MySQL 生成列在动态计算、虚拟列和存储索引之间的选择关系
图2:先看值是否稳定,再决定留在查询层、使用 VIRTUAL,还是为稳定筛选值建立 STORED/索引路径。

为什么能建表还不够:索引表达式也要对得上

生成列建成功之后,我还会专门检查查询写法。MySQL 8.4 支持利用生成列索引优化查询,但表达式要匹配,结果类型也要一致。比如生成列定义是 f1 + 1,查询里写成 1 + f1,优化器不一定把它当成同一个表达式。

CREATE TABLE event_log (
    id BIGINT PRIMARY KEY,
    payload JSON NOT NULL,
    event_name VARCHAR(100) AS (
        JSON_UNQUOTE(JSON_EXTRACT(payload, '$.name'))
    ) STORED,
    KEY idx_event_name (event_name)
); -- 中文说明:JSON_UNQUOTE 让索引列与字符串比较保持一致

EXPLAIN SELECT id
FROM event_log
WHERE event_name = 'signup'; -- 中文说明:验收时确认查询表达式和生成列语义一致

我的验收清单通常只有三步:先确认 DDL 能创建,再插入一行看生成值,最后用 EXPLAIN 看真实查询是否有机会使用索引。只看到“表创建成功”还不算完成,因为表达式可用和查询路径有效是两件事。

最后的取舍:别为了少写一段 SQL 牺牲语义

现在再遇到生成列报错,我会先按“是否只依赖本行稳定数据”筛一遍。是,就继续看函数、类型和索引;不是,就把计算放回查询层、应用层或定时汇总。尤其是年龄、距离今天多久、当前用户身份这类词,天然带着时间或上下文,强行生成列往往只是把问题藏起来。

生成列真正舒服的地方,是把稳定且重复的表达式变成表结构的一部分;它不适合替代所有业务计算。对我来说,这个边界一旦想清楚,MySQL 拒绝某个函数就不再像语法报错,而是在提醒:这列其实没有你想象得那么稳定。

相关问题

生成列一定要用 STORED 才能建索引吗?

不一定。MySQL 8.4 的 InnoDB 支持虚拟生成列上的二级索引,STORED 生成列也可以建立索引;具体选择要结合存储成本、计算成本和查询频率。

把当前时间先存进普通列,再用生成列可以吗?

可以,但那已经改变了语义:你保存的是写入时刻或业务确认时刻,而不是每次读取时的“现在”。列名和注释最好把这个时间点说清楚,避免后续把它误当成实时年龄或实时状态。

参考:MySQL 8.4 官方文档:CREATE TABLE and Generated ColumnsMySQL 8.4 官方文档:Optimizer Use of Generated Column Indexes

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
琥珀色雨夜电车手机壁纸怎么做出湿润反光琥珀色雨夜电车手机壁纸怎么做出湿润反光
上一篇
琥珀色雨夜电车手机壁纸怎么做出湿润反光
Go encoding/csv 如何读取带换行的引号字段
下一篇
Go encoding/csv 如何读取带换行的引号字段
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    98次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    28次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    253次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    180次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    114次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码