娱乐

修改sql语句

时间:2010-12-5 17:23:32  作者:时尚   来源:焦点  查看:  评论:0
内容摘要:改SQL语句这件事,最怕的就是拿到手先动WHERE条件。我见过太多人一上来就加索引提示、改连接方式,结果跑出来结果集跟原来不一样,业务方直接炸锅。真正该做的第一件事,是先把原语句的“意图”拆出来:它到

改SQL语句这件事,修改最怕的修改就是拿到手先动WHERE条件。我见过太多人一上来就加索引提示、修改改连接方式,修改结果跑出来结果集跟原来不一样,修改业务方直接炸锅。修改真正该做的修改第一件事,是修改先把原语句的“意图”拆出来:它到底想查什么、过滤什么、修改聚合什么。修改这个搞清楚了,修改后面改才有方向。修改

举个例子。修改上周同事扔过来一条语句,修改说跑了四十分钟没出结果。修改我扫了一眼,大致长这样:

SELECT a.id, a.name, b.total FROM users a LEFT JOIN ( SELECT user_id, SUM(amount) AS total FROM orders WHERE create_time >= '2024-01-01' GROUP BY user_id ) b ON a.id = b.user_id WHERE a.status = 1 AND a.city = '杭州' ORDER BY b.total DESC;

问题很明显。子查询先把orders全表按时间过滤再聚合,生成一个巨大的中间结果集,然后才跟users做连接。但外层WHERE里对users有status和city两个强过滤条件。这意味着子查询里绝大部分聚合结果根本用不上,白白算了一遍。

改法不是去调连接算法,而是把过滤条件下推。先筛出符合条件的用户,再拿这批用户的ID去聚合订单。改写后:

SELECT a.id, a.name, IFNULL(b.total, 0) AS total FROM users a LEFT JOIN ( SELECT o.user_id, SUM(o.amount) AS total FROM orders o INNER JOIN users u ON o.user_id = u.id WHERE u.status = 1 AND u.city = '杭州' AND o.create_time >= '2024-01-01' GROUP BY o.user_id ) b ON a.id = b.user_id WHERE a.status = 1 AND a.city = '杭州' ORDER BY total DESC;

执行时间从四十多分钟降到十几秒。核心变化就一处:让聚合子查询只处理杭州地区活跃用户的订单。这里有个细节——LEFT JOIN改成子查询内部用INNER JOIN,外层保留LEFT JOIN,是因为要保证没有订单的用户也出现在结果里,total显示为0。如果业务方不要求展示零订单用户,直接改成INNER JOIN还能再快一截。

再说一个常见坑:OR条件。很多人写WHERE a = 1 OR b = 2,优化器直接放弃索引走全表。我一般会拆成UNION ALL,每个分支单独用索引,再在外层去重或聚合。但要注意,拆之前得确认两个分支的结果集没有重叠,否则UNION比UNION ALL多一步去重,性能反而可能更差。这个判断只能靠业务逻辑,数据库帮不了你。

还有一类改法跟性能无关,纯粹是逻辑修正。比如NOT IN遇到子查询里有NULL值,整个条件永远返回UNKNOWN,一行都查不出来。换成NOT EXISTS或者把子查询里的NULL排除掉,结果才对。这种问题不跑数据根本看不出来,语句语法完全合法,但业务逻辑是错的。

我的习惯是改完必做三件事:拿原语句和新语句各跑一遍,用EXCEPT或MINUS比对结果集差异;看执行计划里type列有没有从ALL变成ref或range;最后把EXPLAIN的rows估算值和实际返回行数对一下,偏差超过一个数量级就得重新考虑统计信息是不是过期了。

改SQL不是炫技。能不动就不动,要动就动在刀刃上。大部分慢查询的根因不在写法,而在数据量、索引缺失或者统计信息不准。先看EXPLAIN,再决定改什么。

最近更新
热门排行
copyright © 2026 powered by 力不从心网   sitemap