MySQL中SELECT查询的执行顺序

MySQL中SELECT查询的执行顺序

在日常的数据库开发中,我们经常会写各种复杂的SELECT查询语句。然而,很多开发者对于MySQL实际执行这些查询的顺序并不完全了解。理解查询的执行顺序不仅有助于编写更高效的SQL语句,还能帮助我们更好地优化查询性能和避免常见的错误。

一、SELECT语句的书写顺序 vs 执行顺序

首先,让我们明确一个重要概念:SQL语句的书写顺序和实际执行顺序是不同的

书写顺序

sql 复制代码
SELECT DISTINCT column_list
FROM table_list
JOIN table ON join_condition
WHERE where_condition
GROUP BY column_list
HAVING having_condition
ORDER BY column_list
LIMIT count OFFSET offset

实际执行顺序

sql 复制代码
FROM -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT

二、详解各步骤的执行顺序

让我们通过一个具体的例子来深入理解每个步骤:

sql 复制代码
SELECT DISTINCT 
    d.dept_name,
    COUNT(e.emp_id) as emp_count,
    AVG(e.salary) as avg_salary
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id
WHERE e.hire_date >= '2020-01-01'
GROUP BY d.dept_name
HAVING COUNT(e.emp_id) > 5
ORDER BY avg_salary DESC
LIMIT 10;

1. FROM 子句

执行顺序:第一步

MySQL首先确定数据来源,从指定的表中读取所有数据。

sql 复制代码
FROM employees e

此时会创建一个虚拟表VT1,包含employees表的所有行。

2. JOIN 子句

执行顺序:第二步

如果有JOIN操作,MySQL会根据连接条件合并表。

sql 复制代码
INNER JOIN departments d ON e.dept_id = d.dept_id
  • 生成笛卡尔积
  • 应用ON条件进行过滤
  • 根据JOIN类型(INNER/LEFT/RIGHT)决定保留哪些行
  • 生成虚拟表VT2

3. WHERE 子句

执行顺序:第三步

对JOIN后的结果集进行条件过滤。

sql 复制代码
WHERE e.hire_date >= '2020-01-01'

注意:WHERE子句不能使用聚合函数,因为此时还没有进行分组。生成虚拟表VT3。

4. GROUP BY 子句

执行顺序:第四步

按指定列对数据进行分组。

sql 复制代码
GROUP BY d.dept_name
  • 将VT3中的数据按dept_name分组
  • 每个分组变成结果集中的一行
  • 生成虚拟表VT4

5. HAVING 子句

执行顺序:第五步

对分组后的数据进行过滤。

sql 复制代码
HAVING COUNT(e.emp_id) > 5
  • HAVING可以使用聚合函数
  • 只保留员工数大于5的部门
  • 生成虚拟表VT5

6. SELECT 子句

执行顺序:第六步

选择要返回的列,执行表达式计算。

sql 复制代码
SELECT 
    d.dept_name,
    COUNT(e.emp_id) as emp_count,
    AVG(e.salary) as avg_salary
  • 计算聚合函数
  • 执行表达式
  • 应用列别名
  • 生成虚拟表VT6

7. DISTINCT 子句

执行顺序:第七步

去除重复的行。

sql 复制代码
SELECT DISTINCT ...

生成虚拟表VT7。

8. ORDER BY 子句

执行顺序:第八步

对结果集进行排序。

sql 复制代码
ORDER BY avg_salary DESC
  • 可以使用SELECT中定义的别名
  • 可以使用未在SELECT中出现的列(如果该列在分组中)
  • 生成虚拟表VT8

9. LIMIT 子句

执行顺序:第九步

限制返回的行数。

sql 复制代码
LIMIT 10

最终返回前10条记录。

三、理解执行顺序的重要性

1. 别名的使用限制

由于执行顺序的原因,列别名在不同位置的可用性不同:

sql 复制代码
-- 错误示例:WHERE中不能使用SELECT定义的别名
SELECT salary * 12 as annual_salary
FROM employees
WHERE annual_salary > 50000;  -- 错误!

-- 正确示例:
SELECT salary * 12 as annual_salary
FROM employees
WHERE salary * 12 > 50000;

-- 或者使用子查询
SELECT * FROM (
    SELECT salary * 12 as annual_salary
    FROM employees
) t
WHERE annual_salary > 50000;

2. WHERE vs HAVING

理解执行顺序可以帮助我们正确使用WHERE和HAVING:

sql 复制代码
-- WHERE:过滤行(分组前)
-- HAVING:过滤组(分组后)

-- 错误:WHERE中使用聚合函数
SELECT dept_id, AVG(salary)
FROM employees
WHERE AVG(salary) > 5000  -- 错误!
GROUP BY dept_id;

-- 正确:HAVING中使用聚合函数
SELECT dept_id, AVG(salary)
FROM employees
GROUP BY dept_id
HAVING AVG(salary) > 5000;

-- 性能优化:尽可能使用WHERE
SELECT dept_id, AVG(salary)
FROM employees
WHERE salary > 3000  -- 先过滤,减少分组的数据量
GROUP BY dept_id
HAVING AVG(salary) > 5000;

3. JOIN的优化

理解JOIN在WHERE之前执行,可以帮助我们优化查询:

sql 复制代码
-- 低效:先JOIN所有数据,再WHERE过滤
SELECT *
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= '2024-01-01';

-- 高效:在JOIN条件中尽早过滤
SELECT *
FROM orders o
INNER JOIN customers c 
    ON o.customer_id = c.customer_id 
    AND o.order_date >= '2024-01-01';

四、特殊情况和注意事项

1. 子查询的执行顺序

子查询的执行时机取决于其类型:

sql 复制代码
-- 非相关子查询:先执行子查询
SELECT * 
FROM employees 
WHERE dept_id IN (
    SELECT dept_id 
    FROM departments 
    WHERE location = 'Beijing'
);

-- 相关子查询:对外查询的每一行执行一次
SELECT e1.* 
FROM employees e1
WHERE salary > (
    SELECT AVG(salary) 
    FROM employees e2 
    WHERE e2.dept_id = e1.dept_id
);

2. UNION的执行顺序

sql 复制代码
(SELECT name FROM employees WHERE dept_id = 1)
UNION
(SELECT name FROM employees WHERE dept_id = 2)
ORDER BY name;
  • 先执行各个SELECT
  • 然后合并结果(去重)
  • 最后应用ORDER BY

3. 窗口函数的执行顺序

窗口函数在SELECT阶段执行,但在DISTINCT之前:

sql 复制代码
SELECT DISTINCT
    dept_id,
    salary,
    ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) as rn
FROM employees
WHERE hire_date >= '2020-01-01';

执行顺序:FROM -> WHERE -> SELECT(包括窗口函数) -> DISTINCT

五、性能优化建议

基于执行顺序的理解,我们可以得出以下优化建议:

1. 尽早过滤数据

sql 复制代码
-- 在WHERE中过滤,而不是HAVING
-- 在JOIN条件中过滤,而不是WHERE

2. 合理使用索引

sql 复制代码
-- 为WHERE、JOIN、ORDER BY涉及的列创建索引
CREATE INDEX idx_hire_date ON employees(hire_date);
CREATE INDEX idx_dept_id ON employees(dept_id);

3. 避免在WHERE中使用函数

sql 复制代码
-- 不好
WHERE YEAR(hire_date) = 2024

-- 好
WHERE hire_date >= '2024-01-01' AND hire_date < '2025-01-01'

4. 使用EXPLAIN分析执行计划

sql 复制代码
EXPLAIN SELECT ... ;

六、常见误区总结

  1. 误区 :认为SQL按书写顺序执行 事实:执行顺序是固定的,与书写顺序不同
  2. 误区 :WHERE和HAVING可以互换使用 事实:WHERE过滤行,HAVING过滤组,执行时机不同
  3. 误区 :SELECT中定义的别名可以在WHERE中使用 事实:WHERE在SELECT之前执行,无法使用别名
  4. 误区 :ORDER BY总是最后执行 事实:如果有LIMIT,ORDER BY在LIMIT之前执行

结语

深入理解MySQL SELECT查询的执行顺序是编写高效SQL的基础。通过掌握这些知识,我们可以:

  • 避免常见的SQL错误
  • 编写更高效的查询
  • 更好地进行性能优化
  • 理解查询结果的生成过程

记住核心执行顺序:FROM -> JOIN -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT

相关推荐
天天摸鱼的java工程师8 分钟前
高考放榜夜,系统别崩!聊聊查分系统怎么设计,三张表足以?
java·后端·mysql
Karry的巡洋舰11 分钟前
【数据库】安全性
数据库·oracle
软件测试小仙女1 小时前
鸿蒙APP测试实战:从HDC命令到专项测试
大数据·软件测试·数据库·人工智能·测试工具·华为·harmonyos
exe4521 小时前
jdbc查询mysql数据库时,出现id顺序错误的情况
数据库·mysql
John Song1 小时前
macOS 上使用 Homebrew 安装redis-cli
数据库·redis·macos
数据知道1 小时前
Mac电脑上本地安装 redis并配置开启自启完整流程
数据库·redis·macos
Lonely丶墨轩1 小时前
Redis 缓存策略:借助缓存优化数据库性能并保障数据一致性
数据库·redis·缓存
GUIQU.1 小时前
【Oracle】分区表
数据库·oracle
Wooden-Flute1 小时前
五、查询处理和查询优化
服务器·数据库·oracle
曹牧1 小时前
Delphi中实现批量插入数据
数据库·oracle