sql批处理

sql批处理这名字听着唬人,处理其实说白了就一句话:把一堆sql语句攒成一梭子,处理一次性打出去。处理干这活儿的处理人都知道,单发SQL是处理打靶,批处理是处理扫射——靶子要是一个个瞄,黄花菜都凉了。处理

我头一回正经用批处理,处理是处理在一家做电商报表的公司。每天凌晨两点,处理服务器要跑前一天的处理全量订单汇总。那会儿不懂事,处理写了个循环,处理一条条insert,处理两百万条数据跑了四十分钟。处理后来老同事看了一眼,把我代码删了,换了个写法:先建临时表,攒够一万条批量插入一次,再配上事务包裹。结果呢,四分半跑完。那晚上我盯着屏幕,第一次意识到批处理不是“把循环包起来”那么简单,是得跟数据库商量着来。

批处理的核心逻辑,无非三件事:攒、刷、断。攒,是把零散操作积成块;刷,是选准时机一次性执行;断,是出错时能干净利落地回滚,别留半拉子工程。这三件事听着简单,做起来全是坑。

比如攒。有人图省事,把所有语句塞进一个字符串,用分号隔开,然后execute。看着挺酷,结果某条语句里带了个注释符,或者字段值里藏着分号,整个批次直接崩掉。我见过最离谱的一次,是有人把用户输入的备注直接拼进SQL,里面有个单引号,好家伙,那批处理愣是把后面几十条语句全给“注释”了,数据全写岔了。所以攒之前,得先想清楚:这批次里有没有不可控的输入?有的话,参数化是底线。

再说刷。刷的时机比刷的量重要。有人迷信“越大越好”,一次攒十万条再提交,结果锁表锁到业务方骂娘。实际上,批处理最舒服的节奏是“小步快跑”——每次几百条,提交一次,既不至于把事务日志撑爆,又能让其他查询喘口气。我后来习惯在批处理里加个计数器,每五百条就commit一次,哪怕中途崩了,顶多重跑最后五百条,不至于全盘重来。这招土,但真管用。

断,才是最见功力的地方。批处理最怕的不是报错,是报错之后你不知道错在哪。所以写批处理脚本,第一件事不是写业务逻辑,是写日志。每处理一段,往日志表里插一条记录,标记批次号、当前进度、影响行数。出了事,看日志就知道卡在哪个环节。有人嫌麻烦,觉得日志拖慢速度。可你想想,一次跑两小时的批处理,最后第119分钟崩了,没日志,你只能从头debug,那才是真慢。

还有个细节,很多人忽略——批处理里别用游标。游标是逐行处理的,本质上是把批处理退化成了单发。你要真想逐行算,不如先把数据捞到内存里,用程序语言处理完,再批量写回。数据库擅长的是集合操作,不是循环。你非让它干循环的活,它就用锁和慢查询惩罚你。

说到这,想起去年帮朋友调一个对账批处理。他那边每天凌晨跑一次,把支付平台的对账单跟本地订单比对,差异数据要标记出来。原脚本写了三百多行,全是循环嵌套,跑一次要五十分钟。我给他改了思路:先全量拉进两个临时表,用left join把差异行一次性筛出来,再批量更新状态。脚本缩到六十行,跑一次七分钟。他当时问我一句话,我到现在都记得:“原来批处理不是写步骤,是写集合关系啊?”对,就是这个理。

批处理这东西,写多了你会摸出个脾气:它不喜欢花活,就喜欢直来直去。你声明清楚要处理什么范围,给它足够的资源,让它分批走,出错能退——它就能稳稳当当把活干完。反过来,你想在批处理里炫技,搞动态SQL拼接、搞多层子查询嵌套、搞跨库分布式事务,它分分钟给你颜色看。

最后说个实战习惯。我每次写完批处理,都会故意往数据里塞几条脏数据,看看脚本会不会被带偏。这招叫“故障注入”,听起来高级,其实就是提前惹事,省得半夜被电话叫醒。批处理这行,不出错是运气,能处理错才是本事。你把这层想透了,写出来的脚本自然就稳了。