拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL报错only_full_group_by详解:GROUP BY与sql_mode实战

MySQL报错only_full_group_by详解:GROUP BY与sql_mode实战 先还原一下我之前在现网碰到的场景。凌晨两点左右监控突然弹出一条告警某个统计接口的 Error 率直接飙了上去。拉日志一看满屏都是同一句 SQL 报错Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column testdb.t_user.user_name which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_modeonly_full_group_by翻译成人话就是我的查询里有个user_name列既没有放进GROUP BY也没有用聚合函数包起来MySQL 认为这种查询结果是不确定的于是直接拒绝执行。第一次遇到这个报错的人大概率会一脸懵因为同样的 SQL 在 MySQL 5.6 的老库上跑得好好的怎么一升级到 5.7 就翻车了其实不是你写错了是数据库的默认行为变了。这篇文章就围绕这个报错展开。我会从报错产生的原因讲起把only_full_group_by这条规则彻底讲透再给出几种解决思路的对比最后附上完整的排错记录和我自己踩过的坑。不管你是后端开发、DBA、运维还是正在准备面试只要碰上GROUP BY相关的报错这篇应该都能帮上忙。1. 先看懂报错这个错误到底在“怼”你什么1.1 一个最典型的报错现场先看一段几乎每个后端开发都写过的 SQLSELECT user_name, dept_id, COUNT(*) AS cnt FROM t_user GROUP BY dept_id;这条 SQL 想表达的意思是统计每个部门的人数同时把部门里某个人的名字也带出来。听起来很合理对吧但在only_full_group_by模式下MySQL 直接报错错误信息里明确指出user_name这一列不在GROUP BY子句里也没有被聚合函数包裹。问题就出在这里GROUP BY dept_id执行完之后结果集是“按部门分组”的每个部门只剩一行。但一个部门下面通常有多个员工那这一行的user_name到底应该填谁的名字是第一个入职的还是工号最小的还是随机的SQL 标准认为这种查询的结果是“不确定的”没有业务含义所以直接拒绝执行。MySQL 不是故意为难你而是在提醒你这个查询逻辑本身就有问题。正确的写法应该是SELECT dept_id, COUNT(*) AS cnt FROM t_user GROUP BY dept_id;如果你实在想带出部门里的一个用户名那就必须明确告诉数据库你的意图是取任意一个还是取最大的还是把所有人的名字拼起来。比如MAX(user_name)表示取字母序最大的名字GROUP_CONCAT(user_name)表示把所有人的名字拼成一行ANY_VALUE(user_name)表示随便取一个。1.2 ONLY_FULL_GROUP_BY 的规则本质ONLY_FULL_GROUP_BY是 MySQL 的sql_mode参数里的一种模式它严格遵循 SQL 标准中对GROUP BY的约束。具体规则是SELECT列表、HAVING条件、ORDER BY列表中出现的非聚合列必须要么出现在GROUP BY子句中要么被聚合函数包裹。我这里说的“聚合函数”指的是COUNT、SUM、AVG、MIN、MAX以及GROUP_CONCAT这类函数。所谓“非聚合列”就是那些直接引用的普通列比如t_user.user_name。为什么 MySQL 要制定这么严格的规则核心原因是“确定性”。分组之后每一组对应结果集里的一行如果某个列既不是分组依据也没有聚合含义那它出现的值就是组内任意一行的值。在 MySQL 5.6 及之前的版本里这种情况会“静默允许”并且取哪一行是不保证的。也就是说同一份数据今天查出来user_name是张三明天可能就变成李四了完全取决于优化器怎么执行。这种不确定性在统计报表、对账系统里是致命的。可以用一个生活里的例子来类比老师让你按班级分组统计人数但你要求在报表里列出某个学生的名字。当这个班有 40 个学生时老师不知道该填谁的名字所以要求你必须写清楚“是班级里名字排第一的同学还是任意一个同学”。ONLY_FULL_GROUP_BY就是那个较真的老师。1.3 为什么 MySQL 5.7 之后突然变严了很多老项目遇到这个报错都是在数据库从 MySQL 5.6 升级到 5.7 或者 8.0 之后。原因很简单MySQL 5.7 开始把ONLY_FULL_GROUP_BY加进了默认的sql_mode而 5.6 及更早版本没有。5.6 时代默认的sql_mode通常是空的或者只有STRICT_TRANS_TABLES这类宽松模式。在这个环境下你写出前面那种“带出非聚合列”的 SQLMySQL 不会报错只是默默给你返回一行数据。于是大量历史项目在不知不觉间积累了一批“看起来能跑但逻辑不确定”的 SQL。等升级到 5.7默认配置一变这批 SQL 全部成了定时炸弹只要一执行就报错。所以这个报错的本质是 MySQL 从“宽松模式”走向“标准模式”的过程中把历史遗留问题一次性暴露了出来。从这个角度看报错本身不是坏事它逼着你去审视那些不严谨的 SQL把潜在的数据一致性问题提前暴露出来。如果你把sql_mode里的ONLY_FULL_GROUP_BY直接关掉短期是消停了但那些不确定的查询仍然存在只是把问题又往后推了而已。2. 两条路改SQL让查询合规还是改sql_mode放宽限制2.1 方案A改写SQL让查询完全合规最推荐遇到这个报错第一反应不应该去改数据库配置而是先看看 SQL 本身能不能改。绝大多数情况下SQL 是可以改得更清晰的。如果你只是想要分组统计结果那直接删掉多余的非聚合列。比如一开始那个错误 SQL删掉user_name之后意图就非常明确了按部门统计人数。SELECT dept_id, COUNT(*) AS cnt FROM t_user GROUP BY dept_id;如果你需要展示组内某个字段且不关心具体是哪一行那可以用MIN、MAX或者ANY_VALUE明确表达“随便取一个”。比如下面的 SQL就是按部门分组后随手带一个示范用户名SELECT dept_id, COUNT(*) AS cnt, MAX(user_name) AS sample_name FROM t_user GROUP BY dept_id;MAX(user_name)是标准 SQL 写法语义明确可移植性好。ANY_VALUE(user_name)是 MySQL 5.7 以后提供的函数作用是显式告诉数据库“我不在乎这一行取的是谁你随便给一个”语义上比MAX更直白。但要注意ANY_VALUE不是标准 SQLOracle、PostgreSQL 里没有这个函数如果你要考虑跨数据库迁移优先用MIN、MAX。如果你需要的是“每组内符合某个条件的记录”比如“每个部门最后登录的那个人”情况会更复杂一些。这在 MySQL 8.0 里可以用窗口函数优雅解决SELECT user_name, dept_id, login_time FROM ( SELECT user_name, dept_id, login_time, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY login_time DESC) AS rn FROM t_user ) AS t WHERE rn 1;在 MySQL 5.7 里没有窗口函数可以用关联子查询或者先取分组最大值再关联回原表的方式实现SELECT t1.user_name, t1.dept_id, t1.login_time FROM t_user t1 JOIN ( SELECT dept_id, MAX(login_time) AS max_login_time FROM t_user GROUP BY dept_id ) t2 ON t1.dept_id t2.dept_id AND t1.login_time t2.max_login_time;改写 SQL 的好处是显而易见的查询结果变得确定、符合 SQL 标准、不依赖数据库配置以后无论怎么升级版本都不会再被这个问题卡住。唯一的问题是如果老项目里这样的 SQL 有成百上千条改写的工作量会不小需要分阶段处理。2.2 方案B修改sql_mode临时救急务必谨慎第二种思路是修改sql_mode把ONLY_FULL_GROUP_BY从模式列表里去掉。这样做确实立竿见影再跑原来的 SQL 就不报错了但我必须强调这是“临时救急”手段不是“完美解决方案”。直接关掉这条规则相当于告诉 MySQL别管一个分组里到底该显示哪个值随便选一行返回就行。短期看业务正常了但很多隐患会在后面慢慢冒出来。比如同一个统计接口应用重启前后查出来的结果可能不一样又比如开发环境不报错生产环境却报了错因为两边的sql_mode配置不一致。更危险的是一些人为了图省事直接执行SET GLOBAL sql_mode ;把整个sql_mode清空。这会把STRICT_TRANS_TABLES也一起关掉。严格模式一旦关闭插入超长字符串时 MySQL 会静默截断而不是报错插入非法日期也可能被转成0000-00-00。数据被“静默修改”这种事排查起来可比 SQL 报错痛苦十倍。所以我的态度很明确sql_mode可以改但只适合作为“过渡方案”。先让线上恢复同时把那些有问题的 SQL 一条条改掉改完后重新把ONLY_FULL_GROUP_BY加上才是彻底解决。2.3 方案C用ANY_VALUE()明确表达“不挑食”ANY_VALUE()函数值得单独拿出来说因为它在“报错但确实不关心具体值”的场景里特别好用。它的含义是我明确知道这个非分组列在组内可能有多值但我不关心取哪个请数据库不要报错随便给我一个。SELECT dept_id, COUNT(*) AS cnt, ANY_VALUE(user_name) AS name FROM t_user GROUP BY dept_id;这条 SQL 在only_full_group_by模式下能正常执行也不会影响统计结果。它的存在意义在于有时候你确实只是想“随手展示一个示例值”并不存在逻辑歧义。用ANY_VALUE就是把这种“不在乎”的态度明确告诉数据库比默默关掉整个ONLY_FULL_GROUP_BY要精准得多。但要注意适用边界如果业务逻辑要求这个非聚合列必须和分组列有某种关联比如必须取“最新登录时间对应的用户名”那ANY_VALUE是不行的必须用第 2.1 节里的窗口函数或关联子查询。2.4 怎么选给不同场景的决策清单不同环境下这个问题的最优解法不一样。我结合实际项目场景整理了一份决策清单可以直接参考场景推荐做法理由SQL 逻辑本身就有歧义非聚合列没有明确业务含义改写 SQL删除列或加聚合函数从根源消除不确定性符合 SQL 标准老项目里存在大量违规 SQL短时间内改不完临时修改sql_mode去掉ONLY_FULL_GROUP_BY同时排期改写 SQL先恢复业务再逐步排除历史债务仅需取组内某个任意值用ANY_VALUE或MIN/MAX明确表达意图不破坏全局配置需要取每组内“符合某种条件”的那条记录用窗口函数或关联子查询保证结果确定且正确使用的是云数据库控制台无法修改sql_mode改写 SQL托管环境通常不允许动全局配置改 SQL 是唯一可行路径准备从 MySQL 5.6 升级到 5.7/8.0升级前做全量 SQL 审查提前改写违规 SQL避免上线后突然报错做到平滑升级3. 实操记录从看到报错到彻底解决一步步来3.1 第一步确认当前sql_mode操作之前先看清当前数据库到底用了哪些模式。MySQL 提供了三个方式SELECT GLOBAL.sql_mode; SELECT SESSION.sql_mode; SHOW VARIABLES LIKE sql_mode;执行结果通常类似下面这样ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION这里面的ONLY_FULL_GROUP_BY就是报错元凶。其余几个模式也各有作用比如STRICT_TRANS_TABLES控制严格模式NO_ZERO_DATE禁止插入0000-00-00这样的非法日期ERROR_FOR_DIVISION_BY_ZERO让除零操作报错而不是返回NULL。改配置时要尽量保留这些默认的模式不要整个清空。三个查看命令的细微差别也要讲清楚GLOBAL.sql_mode查看的是全局配置对新建连接生效SESSION.sql_mode查看的是当前连接会话生效的配置优先级比全局高SHOW VARIABLES LIKE sql_mode默认展示当前会话的值。如果你用 Navicat、DataGrip 这类工具默认看到的是你当前连接会话的值。3.2 第二步临时救急用SET命令快速恢复如果线上正在报错想第一时间恢复业务可以先用SET命令临时去掉ONLY_FULL_GROUP_BY。只针对当前会话生效的写法SET SESSION sql_mode STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION;执行完这条之后当前连接再跑原来的 SQL 就不会报错了。但这个操作只对当前连接有效其他连接、其他应用进程不受影响。如果你通过 Navicat 执行了这条 SQL那么只有这个 Navicat 会话生效你的应用连接池里的连接仍然是旧配置。想对所有新建连接生效需要用GLOBAL写法SET GLOBAL sql_mode STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION;GLOBAL修改后新建立的连接会读到新配置但已经存在的连接池里的连接不会变。换句话说如果你改了GLOBAL发现应用还在报错不要慌先重启应用或者手动断开旧连接池问题通常会消失。需要特别提醒的是SET GLOBAL和SET SESSION都只对当前 MySQL 实例运行期生效一旦 MySQL 服务重启配置就会还原成配置文件或默认值。所以临时救急了之后千万别忘了做永久修改否则下一次重启之后旧病复发。3.3 第三步永久生效修改配置文件并重启要让sql_mode永久生效必须修改 MySQL 配置文件。MySQL 的配置文件在 Linux 上一般叫my.cnf在 Windows 上叫my.ini。不同发行版的默认路径不完全一样CentOS/RHEL 通常是/etc/my.cnfDebian/Ubuntu 可能是/etc/mysql/mysql.conf.d/mysqld.cnf还有一些手动安装的实例会放在安装目录下。最稳妥的确认方式是执行mysql --help | grep Default options -A 1打开配置文件后找到[mysqld]段在下面新增或者修改一行[mysqld] sql_modeSTRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION注意两个关键点。第一参数名是sql_mode不是sql-mode有人习惯写中划线会在 MySQL 启动时报错。第二一定要写在[mysqld]段下面如果写到了[client]或者[mysql]段里不会报错但也不会生效因为这是服务端的配置。保存后重启 MySQLsystemctl restart mysqld或者service mysql restart重启之后再验证一次SHOW VARIABLES LIKE sql_mode;看到ONLY_FULL_GROUP_BY已经不在了说明生效了。如果重启后 MySQL 启动失败多半是配置文件写错了。此时先用tail -n 100 /var/log/mysql/error.log看下错误日志常见问题包括sql_mode单词拼错、模式名称拼写错误、用了中文逗号或者配置里写了两个sql_mode后面一个覆盖了前面一个。3.4 Docker和云RDS的特殊处理方式如果 MySQL 跑在 Docker 容器里改配置文件的方式会略有不同。你可以进容器直接修改docker exec -it mysql-container bash然后编辑容器内的/etc/mysql/conf.d/my.cnf改完重启容器。但这么做有个问题容器一旦被删除重建修改就会丢失。更好的方式是把配置文件挂载到宿主机上docker run -d \ -v /data/mysql/config/my.cnf:/etc/mysql/conf.d/my.cnf \ -e MYSQL_ROOT_PASSWORDyour_password \ mysql:8.0修改宿主机上的/data/mysql/config/my.cnf后重启容器即可。这种方式配置可持久化也方便在测试环境之间同步配置。如果用的是云数据库比如阿里云 RDS、腾讯云 MySQL那控制台里一般有“参数设置”或“参数组”入口可以直接修改sql_mode。修完记得在控制台点一下“应用到实例”。但要注意云厂商对部分参数可能锁定了修改权限尤其是高可用架构下主实例和只读实例的参数最好保持一致否则会出现在主库不报错、从库报错的诡异现象。我没有权限修改云数据库的sql_mode时就只剩下改写 SQL 这一条路了。换个角度想这反而是在帮你把 SQL 改规范长远看不亏。4. 踩过的坑和排错笔记4.1 改了配置没生效可能卡在哪这个问题的排错过程我自己经历了很多次。最常见的情况是改了配置文件重启了 MySQL执行SHOW VARIABLES LIKE sql_mode发现还是带ONLY_FULL_GROUP_BY。排查方向主要有三个。第一确认你改的是不是 MySQL 真正读取的配置文件。Linux 上 MySQL 可能会同时加载多个目录下的配置文件后面的配置会覆盖前面的。如果你在/etc/my.cnf里改了但发行版默认还会加载/etc/mysql/mysql.conf.d/*.cnf下的文件而里面有另一个sql_mode就会把你写的覆盖掉。解决办法是在配置文件里用!includedir路径确认实际生效的文件或者干脆用SELECT GLOBAL.sql_mode看当前值和配置文件里的值逐项比照。第二确认修改后真的重启了服务。有些人执行了systemctl reload mysql但sql_mode这类参数只有完全重启才会重新加载。我一般直接执行systemctl restart mysqld不要用reload。第三确认你执行 SQL 的连接没有复用旧配置。这个在 3.2 里也提到过连接池里的连接如果是在修改配置之前建立的会一直持有旧配置。改了sql_mode后应用还报错很多时候不是没生效而是连接没重连。重启应用、或等连接池连接超时自动重建问题就没了。还有一个冷门但非常真实的坑存储过程、视图、函数在创建的时候会把当时的sql_mode固定下来。如果你在ONLY_FULL_GROUP_BY开启时创建了一个存储过程里面有一段有问题的 GROUP BY 查询那么即使你之后修改了sql_mode调用这个存储过程时它内部还是会按创建时的模式执行继续报错。解决方法是ALTER或 DROP 重建这些对象让它们继承新的sql_mode。这一点很多人不知道排查起来非常耗时。4.2 SELECT列、别名、HAVING的坑一并说清这个报错的排查牵扯到 SQL 语义我先列一个最容易踩的坑SELECT *配合GROUP BY。比如SELECT * FROM t_user GROUP BY dept_id;这条 SQL 凡是执行几乎必然触发ONLY_FULL_GROUP_BY报错因为*展开后包含了user_name、login_time等一堆非聚合非分组列。要修这种 SQL不能简单地把*原样保留必须明确列出真正需要的列。如果你确实需要每个部门某个人的完整信息那就要用窗口函数或关联子查询的方式而不是直接SELECT *加GROUP BY。还有别名层面的坑WHERE子句里不能使用SELECT中定义的别名但HAVING可以使用。比如SELECT user_name AS name FROM t_user GROUP BY user_name HAVING name ;是允许的但WHERE name 会报错“Unknown column”。这不是ONLY_FULL_GROUP_BY特有的问题而是 SQL 执行顺序决定的WHERE在SELECT之前执行别名还没生成。HAVING子句中的非聚合列同样需要满足ONLY_FULL_GROUP_BY规则。比如SELECT dept_id, COUNT(*) AS cnt FROM t_user GROUP BY dept_id HAVING user_name 张三;这条 SQL 也会报错因为HAVING里的user_name既不在分组里也没有被聚合函数包裹。如果你要筛选“存在张三的部门”应该写成SUM(user_name 张三) 0或者GROUP_CONCAT(user_name) LIKE %张三%注意写法取决于具体业务意图。4.3 MySQL 8.0下的新变化GROUP BY不再隐式排序窗口函数登场如果你的数据库已经升级到 MySQL 5.7 或 8.0还有一个隐藏变化值得知道GROUP BY的结果不再保证有序。在 MySQL 5.6 及更早版本中GROUP BY dept_id会默认按dept_id排序输出很多人习惯了这一点写完GROUP BY就不再写ORDER BY。但 5.7 之后这个隐式排序被去掉了输出顺序不再确定。如果你依赖“分组后自然有序”请务必在 SQL 末尾显式加ORDER BY。与ONLY_FULL_GROUP_BY配合使用时ORDER BY里出现的非聚合列同样要守规矩。比如SELECT dept_id, COUNT(*) AS cnt FROM t_user GROUP BY dept_id ORDER BY user_name;这条 SQL 会报错因为ORDER BY user_name中user_name不在分组中。解决办法是如果要按用户名排序SELECT里也带上MAX(user_name) AS max_user_name然后ORDER BY max_user_name如果不需要排序直接删掉。MySQL 8.0 引入的窗口函数在很大程度上替代了以前“GROUP BY 非聚合列”的土解法。比如“查每个部门最新登录的人”这类需求窗口函数写得又清晰又高效我在 2.1 节已经给了示例代码。如果你的项目已经跑在 8.0 上遇到这类分组内取行的问题首选窗口函数而不是在GROUP BY的边缘反复试探。4.4 别把sql_mode当万能药关闭模式的隐藏风险这部分是全文最重要的一段忠告。很多人遇到这个报错第一反应就是“把ONLY_FULL_GROUP_BY关掉”。我不否认这在某些场景下是合理的过渡手段但它绝对不应该成为长期默认状态。原因有三层。第一层数据一致性问题。关闭ONLY_FULL_GROUP_BY后同一个分组查询返回哪个非聚合列的值取决于优化器的选择。这个选择可能随着数据量变化、索引变化、MySQL 版本升级而变化。今天你的报表上显示张三明天可能变成李四后天的同事跑同样的 SQL 又变成王五。统计类的业务如果出现这种“灵异变化”基本就没办法对账了。第二层连锁反应。很多人关掉ONLY_FULL_GROUP_BY时不是用精细的模式列表替换而是执行SET GLOBAL sql_mode ;或者直接在配置文件里写成sql_mode。这样一来STRICT_TRANS_TABLES、NO_ZERO_DATE这些保护性规则也被一起关了。紧接下来你会遇到往varchar(50)里插入 60 个字符的字符串MySQL 不报错只是悄悄截断插入2024-02-30这样的日期MySQL 也不报错而是存成0000-00-00。数据在无感知的情况下被污染后续排查成本远高于最开始那个报错。第三层团队规范和协作成本。如果sql_mode被随意改了开发环境、测试环境、生产环境很难保持一致。开发环境不报错代码就能合入到了生产环境因为模式不同开始报错又是一通临时处理。这种不一致会让团队对数据库行为完全没有预期最终所有人都在“环境差异”上浪费时间。我的建议是在任何长期运行的环境里保持 MySQL 默认的sql_mode让ONLY_FULL_GROUP_BY待在它该在的位置。它就像一个严格的代码评审员帮你把不严谨的 SQL 挡在上线之前。至于那些历史遗留的违规 SQL用几周时间逐步改写比长期关闭规则要健康得多。5. 把坑提前堵死写GROUP BY查询的几个好习惯与其每次等报错来了再救火不如从一开始就把规范立在前面。这些年我在团队里推了几个很简单的原则执行下来效果很好。写GROUP BY查询的时候先问自己一个问题分组之后每一行代表什么是“一个部门的统计数据”还是“一个用户的汇总数据”想清楚了这一点再去写SELECT列表。凡是结果集每一行里出现的普通列要么本身是分组依据要么它的值是组内唯一确定的否则就不该出现。一条 SQL 的SELECT列表里合法的列只有三类分组列、聚合函数、常量或函数依赖列。分组列就是GROUP BY里写的那些列聚合函数包裹的列可以随意函数依赖列指的是通过主键或唯一键能唯一确定的那一列。比如按主键id分组时user_name理论上可以通过id唯一确定所以某些情况下 MySQL 也允许但这种写法可读性差我建议不要依赖这个特性。保持一个朴素的习惯SELECT里的每个非聚合列我都能在GROUP BY里找到它的位置。另外尽量让开发环境和线上环境用相同的sql_mode。如果开发环境默认开启了ONLY_FULL_GROUP_BY开发者在本地写完 SQL 跑一遍就能发现问题而不是等到代码上了生产环境才爆雷。这个成本比线上救火低太多了。如果你们团队在做数据库版本升级升级前建议先在测试环境跑一遍核心业务的全部 SQL或者打开慢查询日志和通用日志抓出所有涉及GROUP BY的语句逐一检查合规性再切线上。我在实际项目里的做法是新项目一开始就用 MySQL 5.7 或 8.0 的默认sql_mode不允许任何人随意修改。开发阶段写出违规 SQL本地直接报错改好了再提交。老项目则分两步走先临时放宽sql_mode稳住线上同时把违规 SQL 列入技术债清单规定一个 deadline 全部改完最后再把sql_mode恢复默认。这套流程跑下来之后再也没遇到过半夜被这个报错叫起来的局面。最后再分享一个小技巧如果你的错误信息里提到了Expression #1、Expression #2这些序号可以直接定位到SELECT列表的第几个字段。报错说的是第 1 个字段那就是SELECT之后的第一个字段不对说的是第 5 个字段就去找第 5 个字段。这个信息在排查长 SQL 的时候特别省时间不用把整条 SQL 翻来覆去看半天。别问我为什么知道问就是曾经在一条十几行字段的统计 SQL 里逐一排查过。
返回列表