复盘之我在金仓生产环境踩过的SQL暗坑,和攒了六年的编码规矩

引言

冬夜故障复盘:我在金仓生产环境踩过的SQL暗坑,和攒了五年的编码规矩太多人做开发,都抱着"能跑就行"的心思。SQL写出来,页面能出数据,就算交差。至于规不规矩、有没有隐患、数据量涨上去会不会崩,没人多想。测试环境那点数据,再烂的写法也跑不出毛病;可一到生产,百万千万级的数据压上来,所有藏在水面下的坑,全都会翻上来。轻则接口卡几秒,重则整库打满、数据算错,更有甚者忘写WHERE条件,一执行就抹掉半表数据,回滚都要熬半宿。

电科金仓是国内完全自研的国产数据库,优化器、稳定性、性能上限都经得住核心业务的考验。可再结实的车,也架不住有人一直挂着一档猛踩油门。今天我就把这些年踩过的坑、见过的事故,都写出来,再把编码规矩一并说透。

一、那些藏在代码里的暗坑,每一个都熬过人

头一回栽在SELECT *上,是深秋的人事系统

那是家国企的人事系统,上线俩月,员工列表越来越慢,到最后点一下要等七八秒。用户说系统越用越卡,领导催着优化,开发说"没改什么东西啊"。

我抓了SQL一看,就是最简单的分页查询,写了个SELECT *。那张表三十多个字段,里头有个resume_text的大文本列,存的是个人简历,单条就几十KB。一页查二十条,光往外传数据就要好几兆,磁盘读、内存占、网络传,全被这没用的大字段拖慢了。

后来改成只查页面要的姓名、部门、岗位、入职时间八个字段,接口响应直接从七秒掉到八十毫秒。

其实哪是系统变慢了,是从一开始就埋着雷。图省事写个星号,不用一个个敲字段名,当下是快了,可后面要还的债,多着呢。无用的大字段平白耗着IO,覆盖索引彻底用不上,表加个减个字段还容易引发程序异常,连带着敏感数据也跟着往外漏------薪资、身份证这些,本来页面用不着,跟着星号全查出来了,过等保的时候全是问题。

左连接写不对,财务报表差了十七万

这桩事我记得最清楚,因为当时甲方财务总监的电话打过来,声音都急了,说月度报销汇总差了十七万,下周审计就要进场,必须天亮前查出原因。

我们熬了两个通宵。一开始怀疑迁移工具丢数据,来回导了三遍报销表,单表行数、金额、主键全对得上;后来拆SQL,单查部门表没问题,单查报销表也没问题,一关联就少数据。最后两边抓执行计划对比,才发现蹊跷:开发把报销状态的过滤条件,写在了WHERE子句里。

金仓的优化器是真严谨,一眼就看出来这是个空值拒绝条件,左连接产生的空行全会被过滤掉,直接就做了外连接消除,把LEFT JOIN优化成了内连接。没有报销的部门,自然就从报表里消失了。

开发还不服,说"我写的明明是左连接,以前在旧库跑了好几年都没事"。可他不知道,旧库优化器偏保守,没认出这个可优化的场景,稀里糊涂保留了左连接的样子,不是结果对,是没能力优化罢了。真按SQL标准说,把右表过滤条件写在WHERE里的左连接,本来就该和内连接一个结果。

那天改完SQL,把状态条件挪到ON子句后面,两边数据终于对上的时候,窗外天都蒙蒙亮了。

隐式转换最冤枉:明明建了索引,就是不走

零售会员系统那次也有意思。迁完库之后,会员手机号查询巨慢,开发拍着胸脯说"我建索引了啊"。

我拉执行计划一看,确确实实是全表扫描。再瞅SQL,phone字段明明是VARCHAR类型,代码里传参没加引号,硬生生传了个数字进去。字段要做类型转换才能比对,相当于在索引列上套了层函数,索引自然就废了。

就改一行代码,给参数加上单引号,查询耗时从三秒变成两毫秒。

说冤枉也真冤枉,逻辑没写错,索引也建了,就因为类型没对上,性能差了上千倍。可细说起来也不冤,写的时候多留心一眼类型,哪至于出这种问题。

NOT IN碰上空值,几千条数据凭空"消失"

政务培训系统的坑,藏得最深。上线半个月,业务部门说"未培训人员名单"是空的,所有人都完成培训了,还准备报上级表扬。结果我进库一查,培训表里有条脏数据,人员ID是NULL。

就这一个NULL,让NOT IN的查询整个返回了空集。SQL的三值逻辑就是这样,只要子查询里有一个空值,整个判断就成了未知,WHERE条件通不过,一行数据都出不来。几千个没培训的人,就这么被"漏掉"了,差点影响上级考核。

测试的时候永远测不出来------谁会故意往测试数据里塞NULL呢?可生产里的脏数据,从来不会跟你打招呼。

大偏移量分页:翻到一百页就卡得动不了

日志系统那次,运营人员导出数据,翻到后面几页就超时。开发说"不就查十条数据吗,怎么这么慢"。

我看了眼SQL,LIMIT 200000, 20

哪是查十条啊,是要先扫二十万条数据,再通通扔掉,只留最后二十条。大半的IO和CPU,全花在了没用的数据上。

改成游标分页,带着上一页最后一条的日志ID往下查,不管翻多少页,都是毫秒级出来。

很多人写分页写惯了,从来没想过,偏移量越大,性能垮得越厉害。前几页快,不代表一百页、一千页也能快。

最险的一次:忘加WHERE,全表用户被禁用

这是最惊心动魄的一次。开发上线改配置,写UPDATE语句的时候,手滑删了WHERE条件,直接执行了。整个用户表的状态,全被改成了禁用。

那天中午,整个系统没人能登录,投诉电话打爆了运维台。我们紧急回滚备份,恢复数据,折腾了三个多小时才理顺。项目组当月的绩效,直接打了对折。

写更新删除的时候,多瞟一眼WHERE条件,不过一秒钟的事。可真忘了,就是天大的事故。

二、攒了五年的规矩:都是摔过跤摸出来的门道

踩的坑多了,我慢慢攒出了一套规矩。不是什么高大上的行业标准,也不是抄来的官方文档,全是一次次熬夜、一次次复盘,实打实攒出来的经验。

我给团队里的开发讲,照着做,不敢说百分百不出问题,至少能避开九成以上的低级坑。

查数据:别贪多,用多少查多少

我头一条规矩,就是永远别写SELECT *

页面上要几个字段,就查几个字段。没用的大文本、敏感的薪资身份证,别跟着一起查出来。省IO、省网络,还能用上覆盖索引,性能翻几倍。

还有,查询尽量带WHERE条件。别动不动就查全表,列表也好、统计也罢,加个时间范围、状态过滤,能走索引就走索引,别让数据库陪着你扫全表。

做关联:语义要明,别糊里糊涂

左连接和内连接,该用哪个就用哪个。需要保留左表全部数据,再用LEFT JOIN;只取两边匹配的,就老老实实写INNER JOIN。别图省事全写左连接,到最后自己都分不清语义。

最关键的一条:左连接里,右表的过滤条件,务必写在ON子句后面。别随手扔到WHERE里,哪天优化器给你消除了外连接,数据少了都不知道为什么。

表多了一定要起别名,字段前面都带上别名。不然重名字段一多,报错都是轻的,查错字段才要命。

还有,别用NOT IN,通通换成NOT EXISTS。就一条,能躲开空值带来的灭顶之灾,性能也更好。

用索引:别建完就不管了,写SQL要想着它

建了索引不代表万事大吉,写法不对,索引就是摆设。

索引列上别套函数、别做运算、别乱转类型。比如要查某天的数据,别写DATE(create_time) = '哪天',改成时间区间比对,索引就能用上。

模糊查询别把百分号放前面,前置通配符索引必失效。真要前后模糊查,数据量大就上全文检索,别拿LIKE硬扛。

复合索引记着最左匹配,查询条件里尽量带上最左边的列,别让索引白建了。

分组排序:结果要准,别靠运气

GROUP BY之后,SELECT里就留分组字段和聚合函数。别图省事往里塞别的字段,数据库随机返回值,今天对明天错,全靠运气。

排序尽量用索引字段,大结果集排序没索引,慢得超乎想象。

还有,排序里有空值的,务必写上NULLS FIRST还是LAST。别指望数据库默认行为,换个库、换个版本,顺序就变了,分页翻来覆去都是重复数据,用户只会觉得系统难用。

分页写入:稳字当头,别图快

分页别用大偏移量,数据量上来必崩。能用游标分页就用游标分页,带着上一页的锚点ID查,性能永远稳。

插入数据的时候,字段名要写全。别图省事只写值,表一加字段、一改顺序,插入就乱套。

更新删除是天大的事,WHERE条件一定要有。执行前先用同样的条件SELECT一下,看看影响的行数对不对。重要操作开个事务,确认没问题再提交。大批量的操作就分批跑,一次处理一千条,别一上来就锁全表,把业务全堵死。

空值处理:提前兜住,别等出问题再改

字符串拼接、数值计算,碰上NULL很容易全变成空。用COALESCE提前把NULL转成默认值,该是空串是空串,该是0是0,结果稳当。

判断空值就用IS NULL,别写= NULL,写了也白写,永远查不出数据。

表设计的时候,能给默认值就给默认值。状态默认0,数字默认0,字符串默认空串。NULL值越少,踩坑的机会就越少。

写代码:人能看懂,比什么都重要

关键字大写,表名字段小写,长SQL该换行换行,该缩进缩进。自己写的SQL,过半个月再看,得一眼能看懂。

复杂的SQL、特殊的业务逻辑,务必加注释。为什么加这个条件、为什么这么写,写清楚。不然过俩月自己都忘了,更别说接手的人。

最后一句:别拿SQL写业务逻辑。循环、判断、复杂计算,放代码里去做。SQL就干它该干的事:存数据、查数据,简单、干净、高效。

三、规矩好定,落地才是真难

规矩好写,真要让全团队都照着做,不容易。

我也见过不少团队,规范文档写了几十页,没人看,没人管,写代码还是怎么省事怎么来。

这些年摸下来,有几个笨办法,倒是挺管用。

头一个,代码评审必查SQL。核心功能上线,我都要跟着过一遍SQL。不用全查,就看更新删除、复杂查询、统计报表这几类。拦住一个低级错误,可能就少一次凌晨故障。

第二个,上线前看一眼执行计划。核心查询、大数据量的SQL,测试环境抓一下执行计划,看看走没走索引、是不是全表扫。几秒钟的事,能省后面好多麻烦。

第三个,每周巡检慢SQL。金仓开着慢日志和系统统计视图,每周拉一次慢SQL清单,发给对应开发整改。很多问题不是一上线就崩,是慢慢变慢的,早发现早改,就不会拖成事故。

最后,常给大家讲讲踩过的坑。不是所有人都故意写不规范的SQL,很多人是真不知道这么写会出事。多讲几次真实的事故、熬过夜的经历,大家有了概念,自然就上心了。

四、写在最后

做这行越久,越觉得敬畏心重要。很多故障回头看,都不是什么天大的技术难题,就是写的时候少想了一步、偷了个懒、觉得"应该没事"。

可生产环境从来不会跟你讲应该。数据量上来了、并发上来了,所有的侥幸,都会变成凌晨的电话、满屏的告警、和熬不完的夜。

电科金仓作为国产数据库,内核做得很扎实,优化器也严谨,给我们兜住了很多底层的问题。但再稳的数据库,也需要规范的SQL来配合。

相关推荐
Dylan的码园1 小时前
从Excel到数据库:数据分析全流程与Kettle ETL实战指南
数据库·数据分析·excel
l1t1 小时前
DeepSeek总结的DuckLake 架构深度剖析-1
数据库·架构
花生了什么事o1 小时前
分布式 ID 生成方案:从数据库自增到雪花算法
数据库·分布式·算法
纪伊路上盛名在1 小时前
NVML ERROR_ RM has detected an NVML_RM version mismatch
linux·数据库·gpu·驱动
nVisual1 小时前
01-环境监控集成方案
运维·服务器·开发语言·网络·数据库·数据中心布线·综合布线管理软件
早点睡啊Y2 小时前
精读 LangChain 官方文档(三):
服务器·数据库·langchain
zzzzzz3102 小时前
当老板说「数据库里加个字段就行了」时,我在想什么——一个后端开发的奇葩需求大赏
数据库·人工智能·产品经理