为什么同事写的SQL又快又清晰?揭秘子查询与CTE的终极对决!
写SQL最怕啥?
同事离职半年后,留下一句“跑不动的老报表”,打开一看,两百六十行子查询层层套娃,眼睛当场花。
上周五,我花俩小时把这份遗产拆成三段CTE,速度从四十秒飙到三秒,老板以为我改机器了,其实只是把嵌套地狱拆成乐高块。
先说最直接的,子查询像俄罗斯套娃,一层叠一层,括号长得比代码还多。
每次加需求都得从最里头往回退着改,改完一跑,发现结果空了,原因却是外边忘了NOTIN遇上NULL。
CTE就不一样了,每段查询给它起个名字,像写函数先定义变量,脑子不用一次性装整个图,改一段验证一段,报错直接定位名字,五分钟修完下班。
性能也不是玄学。
PostgreSQL12以后,内联CTE默认生效,系统会把CTE当临时视图直接塞进执行计划,省掉落盘那一步。
你只要在查询前后加EXPLAIN,看关键字“CTE Scan”还是“SubqueryScan”,就能判断数据库有没有偷懒。
昨天我刚踩一脚坑:MySQL8.0.22之前的老版本把递归CTE统统物化,一张十万行的表差点吃光测试库内存,升级补丁后瞬间冷静。
说到递归CTE,比JOIN自己三次省事太多。
上回我们找组织架构里所有能审批老板报销的人,原来写三遍UNIONALL,现在WITHRECURSIVE两句搞定,树形层级直接数出来,顺带算出每个人到老板的距离,连写带跑十分钟,HR小姐姐直接把我拉进午饭群。
更顺滑的是CTE+窗口函数组合。
先WITH出今年每月销量明细,再外面加ROW_NUMBER()把每家店每月排名算出来,最后WHERE筛前十名,一层层台阶似的往下走,谁都能看懂。
子查询要一次性写完这段,等于让自己脑内跑一个嵌套回形针。
不同库脾气得摸透。
Oracle19c之前,递归CTE默认走磁盘缓存,跑大图记得调workarea_size_policy;SQLServer愿意把CTE当内联视图,却不一定下推谓词,得看实际计划,有时加个OPTION(RECOMPILE)才肯动。
旧PG和MySQL5.7干脆没有CTE,这时候老老实实JOIN,别想不开。
工具别偷懒。
我把所有CTE模块写成独立文件,git一拉就能看到谁动了哪一块。
pgAdmin里单步运行,断点设在每个CTE结尾,像调试Python一样简单。
每月安排两小时“SQL大扫除”,看见超过三层的括号就拆,三个月下来,新同事再也不用问“这段是人是鬼”。
一句话:子查询是迷宫,CTE是路标。
把复杂SQL拆成带名字的积木,跑得快、好改、还不会被后来的人骂。