当前位置: 首页 > news >正文

上线前能跑,上线后挂了,三个隐性的SQL陷阱

上线前能跑,上线后挂了,三个隐性的SQL陷阱

上线的第二天,功能突然不工作了

前年做了一个国产化迁移项目,从Oracle换到金仓。系统上线之后,头两天风平浪静。第三天开始,有个模块偶尔报错,再过一天直接崩溃了。最开始只是偶尔返回空结果,后来直接不干活了。

业务方很着急:“你们换数据库是不是把逻辑搞乱了?”

我查了半天代码,最后发现是WHERE子句里函数调用顺序的问题。

Oracle里的写法是这样的:

SELECT*FROMmy_tableWHEREid1=pkg_abc.get_id()ANDpkg_abc.set_id(10)=1;

这个逻辑很明显:先给变量赋个值,再拿这个值去查数据。

在Oracle里,条件是从左往右执行的。get_id()先跑,变量没值,查不到东西。但因为set_id(10)=1会在get_id()之前被先执行(Oracle里赋值函数被当作常量条件优先处理了),所以实际能跑通。

到了金仓上,执行顺序变了。金仓在兼容性设计上虽然也按从左到右处理,但这种“赋值函数”和“取值函数”混在一起的情况,执行结果极其不稳定。刚开始还能返回几条,后来干脆全返回空。

问题的根源在于:优化器会根据代价判断执行顺序。有时候它觉得get_id()更便宜,就先跑get_id(),变量还没赋上,结果自然是空。有时候它觉得set_id()更便宜,先跑了,数据又能出来。

结果就是同一个SQL,有时候行有时候不行。

更坑的是,如果当前会话里之前执行过正确的SQL,变量已经被赋过值了,后面再跑错误的SQL也能拿到数据。这会造成“测试通过了”的假象,上线之后才发现问题。

后来我重写了一下,把这两个函数调用分开了:

-- 第一步:设置值SELECTpkg_abc.set_id(10)FROMDUAL;-- 第二步:查询数据SELECT*FROMmy_tableWHEREid1=pkg_abc.get_id();

两步走,逻辑清晰,不管在哪个数据库上跑都一样。

这件事之后我学乖了:WHERE子句里千万别放有副作用的函数。什么叫副作用?就是会改数据、改状态、改变量值的函数。这类函数应该单独执行,不要跟查询混在一起。

迁移后少了一条数据

另一个项目,从MySQL迁到金仓。

迁移完第二天,业务方反馈某个报表少了一条数据。查了半天,发现是LEFT JOIN的问题:

SELECT*FROMorders oLEFTJOINrefunds rONo.id=r.order_idWHEREr.status=1;

这条SQL的意思是:查所有订单,有退款的带上退款信息,没退款的就算了。

MySQL里跑出来1280条,金仓里只有1123条,少了157个没退款的订单。

原因我后来才搞清楚:金仓的优化器会把LEFT JOIN转成INNER JOIN。WHERE条件里有r.status = 1,右表没匹配的行r.status是NULL,NULL不等于1,这些行会被干掉。优化器一算:既然这些行最后都要被过滤掉,我直接转成INNER JOIN算了。

逻辑上没错,但业务语义变了。

正确的写法应该是把条件挪到ON里:

SELECT*FROMorders oLEFTJOINrefunds rONo.id=r.order_idANDr.status=1;

这样优化器就没理由消除了,因为连接的时候右表已经过滤过了,左表的所有行都能保住。

后来我养成了个习惯:凡是外连接,右表的过滤条件一律放ON里。除非你确实想把没匹配的行过滤掉,那直接用INNER JOIN就行了。

类型不匹配带来的性能爆炸

还有一次,迁移完发现某个查询慢得离谱。原系统里毫秒级响应,到了金仓上变成几十秒。

查了一圈,发现是数据类型的问题。

原SQL里有个条件:

SELECT*FROMordersWHEREorder_no='123456';

order_no在表里是VARCHAR类型,传参传的是字符串,没问题。

但另一个地方是这么写的:

SELECT*FROMordersWHEREorder_no=123456;

传的是数字。MySQL里能做隐式类型转换,VARCHAR跟数字比的时候,会把字符串转成数字再比较。索引还能用上。

金仓的处理方式不一样。数字和VARCHAR比较的时候,会把VARCHAR转成数字。但问题是,这个转换是逐行执行的,索引用不上了,只能全表扫描。100万条数据,全部扫一遍,几十秒就出去了。

查了一下执行计划,确实是全表扫描。

改起来很简单,统一用字符串就行:

SELECT*FROMordersWHEREorder_no='123456';

但问题在于,代码里这种地方太多了。有些是前端传过来的参数类型不一致,有些是ORM自动生成的SQL,有些是不同模块的开发写了不同的写法。

最后我把所有传参的地方都统一了,确保传的是字符串。然后加了一条规则:代码里传参必须跟字段类型一致,不能依赖隐式转换。

总结一下

这三个陷阱有个共同点:在原来的数据库上能跑,到了新数据库上就不行了。

第一个是函数调用顺序的问题。Oracle里能依赖执行顺序的写法,到了金仓上就不稳定了。解决办法是把有副作用的函数移出WHERE子句。

第二个是优化器行为差异的问题。MySQL不做的优化,金仓做了。外连接的右表条件,放到ON里就是安全的。

第三个是隐式类型转换的问题。MySQL里能用的隐式转换,金仓里不一定能用,索引可能失效。保持类型一致,不要图省事。

后来我总结出几条迁移排查的经验,不一定全面,但至少能帮你少走些弯路:

先看执行计划。慢查询、结果对不上,第一件事就是拉执行计划。一看计划就知道优化器干了什么。

再看函数调用。WHERE子句里有自定义函数吗?函数里有状态变更吗?有的话先改掉。

最后看类型匹配。字段是什么类型,传参就用什么类型。不要依赖隐式转换。

那次迁移之后,我学到一个东西:不同数据库之间的差异,不只是在SQL语法上,更在底层的行为逻辑上。写过五年Oracle的人,真不一定能写好金仓的SQL。好在这些坑踩过一次之后,下次就知道怎么绕过去了。

http://www.jsqmd.com/news/1215451/

相关文章:

  • 湖州德清县莫干山民宿打井,山上岩石井施工难度大吗?山区民宿打井经验分享 - 瑞溪泉水利
  • Power BI | 五步设好报表数据权限
  • 2026市政绿化杆直销怎么选?看准这三点不踩坑
  • 求职辅助工具的技术拆解:AI找工作与网申自适应填充
  • 不同蔓越莓补给产品适配人群差异解析
  • 深入解析C2000 ePWM模块:从核心原理到电机控制实战
  • 性价比高的商城小程序哪家好
  • 纪实感与安全感才是婚礼跟拍的关键
  • 开源恶意域名情报库 2026-7-17
  • 2026赤峰宣传片制作公司TOP5权威排名深度实测:独家十维评分+TVC广告与短视频制作报价对比,附选型避坑权威指南 - 影视产业研究
  • 庆阳公务员笔试报班避坑指南:从选机构到跟课全流程防雷
  • Kimi K3 技术拆解:2.8万亿参数开源模型背后的KDA+AttnRes架构创新
  • 2026辽源活动跟拍公司五大服务商权威排名深度实测 | 真实报价对比+云摄影与活动摄影服务评测,附行业选型指南 - 影视产业研究
  • 铝箔盒装月饼怎么自动封口?放盒、覆膜与连续封口流程
  • 一文读懂CRM:客户关系管理系统基础指南
  • 后端:SpringBoot 前端:Vue.js 数据库:MySQL 8.0 社区养老服务老人帮扶系统 社区养老服务springboot老人帮扶系统vue前后分离Java源码mysql
  • 思源宋体CN:零成本提升中文设计档次的7种字重解决方案
  • 外贸独立站东南亚加速实操,360CDN+Nginx 配置优化指南
  • Adobe-GenP 3.0终极指南:如何快速解锁Adobe全家桶的完整教程 [特殊字符]
  • 2026服务器热管理阀门选型白皮书:关键指标与性能实证
  • 【企业级Cursor远程开发标准流程】:金融/大厂团队已验证的4层安全加固+自动同步策略
  • 中教睿海教育咨询(北京)有限责任公司品牌权威解读:军队文职培训领域的垂直深耕者
  • 2026赤峰活动跟拍公司TOP5权威排名深度实测:独家十维评分+照片直播与云摄影服务评测,附行业选型避坑指南 - 影视产业研究
  • Vue2 与 Vue3 自定义指令详解:钩子变化、迁移指南与实战示例
  • Claude代码教学失效真相(92%开发者踩坑的4类反模式)
  • 哪些场景不适合用 AI?这比“能用在哪”更重要
  • 信息爆炸时代,普通人如何精准获取有效信息?附完整渠道清单与筛选方法论
  • 【核反应】基于matlab核衰变粒子的三维仿真【含Matlab源码 15894期】
  • 39-设置开关退出就复原-用偏好写队列和回滚状态兜住
  • 服装店大货下单前,怎么判断供应商能不能稳定补货?