在MySQL中,SQL语句的执行顺序与书写顺序不同,理解这一点对编写高效查询和排查问题至关重要。以下是详细说明及示例:
SQL执行顺序(逻辑处理阶段)
FROM & JOIN
- 确定数据来源,处理表连接,生成初始数据集。
- 子查询在此阶段执行,形成临时表。
WHERE
- 过滤行级数据,排除不满足条件的行。
- 注意:聚合函数(如
SUM
)不能在此阶段使用。
GROUP BY
- 按指定列分组,为聚合计算做准备。
HAVING
- 过滤分组后的数据,只能使用
GROUP BY
中的列或聚合函数。 - 注意:严格按标准SQL,不能直接使用
SELECT
中的别名,但MySQL允许部分例外。
- 过滤分组后的数据,只能使用
SELECT
- 选择最终输出的列,计算表达式,生成别名。
- 窗口函数在此阶段处理。
DISTINCT
- 去重操作,基于
SELECT
的结果集。
- 去重操作,基于
ORDER BY
- 按指定列排序,可使用
SELECT
中的别名。
- 按指定列排序,可使用
LIMIT & OFFSET
- 分页操作,限制返回的行数。
示例拆解
查询需求
找出2023年订单总金额超过1000的用户,按总金额降序取前10名。
SQL语句
SELECT
u.user_id,
SUM(o.amount) AS total_amount
FROM
users u
JOIN orders o ON u.user_id = o.user_id
WHERE
o.order_date >= '2023-01-01'
GROUP BY
u.user_id
HAVING
total_amount > 1000
ORDER BY
total_amount DESC
LIMIT 10;
执行步骤分析
FROM & JOIN
- 先执行
FROM users
和JOIN orders
,将users
和orders
表连接,生成包含所有匹配行的临时表。
- 先执行
WHERE
- 过滤
order_date >= '2023-01-01'
的订单,排除非2023年的数据。
- 过滤
GROUP BY
- 按
user_id
分组,每个用户的所有订单被聚合为一组。
- 按
HAVING
- 过滤分组后
total_amount > 1000
的用户。 - 注意:此处
total_amount
是SUM(o.amount)
的别名,MySQL允许这种写法,但标准SQL需直接使用SUM(o.amount) > 1000
。
- 过滤分组后
SELECT
- 计算每个用户的
SUM(o.amount)
,并赋予别名total_amount
。
- 计算每个用户的
ORDER BY
- 按
total_amount
降序排列结果。
- 按
LIMIT
- 返回前10条记录。
关键注意事项
别名作用域
WHERE
和HAVING
中不能直接使用SELECT
中的别名(MySQL部分支持,但依赖数据库实现)。ORDER BY
和LIMIT
可以使用别名,因为它们执行在SELECT
之后。
性能优化
- 尽量在
WHERE
中提前过滤数据,减少GROUP BY
和HAVING
处理的数据量。 - 避免在
WHERE
和JOIN
中使用复杂表达式,可能影响索引使用。
- 尽量在
聚合函数与窗口函数
- 聚合函数(如
SUM
)在GROUP BY
后生效。 - 窗口函数(如
ROW_NUMBER()
)在SELECT
阶段处理,但可配合OVER()
在分组后计算。
- 聚合函数(如
通过理解执行顺序,可以更高效地编写查询,并避免因逻辑错误导致的性能问题或结果异常。