MySQL 查询语句的执行顺序

MySQL 查询语句的执行顺序与书写顺序完全不同,理解这一点对编写高效 SQL 和排查错误至关重要。以下是完整执行顺序(含底层原理和示例):


标准执行顺序(逻辑阶段)

1. FROMJOIN     -- 确定数据源,生成虚拟表 VT1
2. ON               -- 应用连接条件,过滤 VT1 → VT2
3. WHERE            -- 行级过滤,生成 VT3
4. GROUP BY         -- 分组聚合,生成 VT4
5. HAVING           -- 组级过滤,生成 VT5
6. SELECT           -- 选择列并计算表达式,生成 VT6
7. DISTINCT         -- 去重,生成 VT7
8. ORDER BY         -- 排序,生成 VT8
9. LIMIT/OFFSET     -- 分页,生成最终结果 VT9

详细阶段解析(含底层原理)

1. FROM & JOIN
  • 作用
    加载所有表数据,执行 CROSS JOIN(笛卡尔积)。
  • 生成表VT1(所有行的组合)
  • 示例
    FROM users 
    JOIN orders ON users.id = orders.user_id
    -- 先做笛卡尔积(users × orders)
    
2. ON(连接条件)
  • 作用
    过滤 VT1,仅保留满足连接条件的行(INNER JOIN/LEFT JOIN 逻辑在此生效)。
  • 生成表VT2
  • ⚠️ 陷阱
    LEFT JOINON 条件不满足时,右表补 NULL(不影响左表数据)。
3. WHERE
  • 作用
    VT2 进行行级过滤(不包含聚合函数)。
  • 生成表VT3
  • 🚫 禁止操作
    SELECT name, SUM(sales) 
    FROM orders
    WHERE SUM(sales) > 1000  -- 错误!WHERE 不能使用聚合函数
    GROUP BY name;
    
4. GROUP BY
  • 作用
    按指定列分组,计算聚合函数(如 SUM()COUNT())。
  • 生成表VT4(每组单行,含聚合结果)
  • 示例
    GROUP BY department  -- 按部门分组,计算每部门工资总和
    
5. HAVING
  • 作用
    对分组后的 VT4 进行组级过滤(可安全使用聚合函数)。
  • 生成表VT5
  • 正确示例
    HAVING AVG(salary) > 5000  -- 过滤平均工资超 5000 的部门
    
6. SELECT
  • 作用
    • 选择最终输出的列
    • 执行表达式计算(如 salary * 1.1
    • 为列设置别名(此时别名不可用于 WHERE
  • 生成表VT6
  • ⚠️ 注意
    SELECT name, salary * 12 AS annual_salary
    FROM employees
    WHERE annual_salary > 100000; -- 错误!WHERE 阶段别名未生成
    
7. DISTINCT
  • 作用
    VT6 去重(若存在 GROUP BY,可能已隐含去重)。
  • 生成表VT7
8. ORDER BY
  • 作用
    按指定列排序(唯一可使用 SELECT 别名的地方)。
  • 生成表VT8(有序结果集)
  • 💡 原理
    排序可能消耗大量内存,对大表需谨慎。
9. LIMIT / OFFSET
  • 作用
    VT8 中截取指定行(分页查询的最后步骤)。
  • 生成表VT9(最终结果)
  • 性能提示
    LIMIT 1000, 10 会先跳过 1000 行(效率低),推荐用 WHERE id > last_id 优化。

关键流程图解

FROM + JOIN
ON
WHERE
GROUP BY
HAVING
SELECT
DISTINCT
ORDER BY
LIMIT/OFFSET

经典示例分析

查询:每个部门工资超 6000 的员工数(仅显示超过 2 人的部门)
SELECT 
    department, 
    COUNT(*) AS emp_count   -- 步骤6:选择列 & 别名
FROM employees             -- 步骤1:加载表
WHERE salary > 6000        -- 步骤3:过滤行
GROUP BY department        -- 步骤4:按部门分组
HAVING COUNT(*) > 2        -- 步骤5:过滤组
ORDER BY emp_count DESC    -- 步骤8:按别名排序
LIMIT 10;                  -- 步骤9:分页
执行顺序拆解
步骤操作结果
1FROM employees加载所有员工数据
3WHERE salary > 6000过滤出工资 >6000 的员工
4GROUP BY department按部门分组,计算每组的行数
5HAVING COUNT(*) > 2过滤出员工数 >2 的部门
6SELECT department, COUNT(*)选择列并命名为 emp_count
8ORDER BY emp_count DESC按员工数降序排序
9LIMIT 10返回前 10 条结果

性能陷阱与优化建议

  1. 尽早过滤数据

    • 将严格条件放在 WHERE(而非 HAVING),减少 GROUP BY 处理量。
    • 例:WHERE date > '2023-01-01'HAVING date > '2023-01-01' 高效得多。
  2. 避免 SELECT *

    • SELECT 阶段获取的列越少,内存和 I/O 开销越小。
  3. 谨慎使用排序

    • ORDER BY 可能触发文件排序(filesort),对大表用索引优化排序。
    • 例:为 ORDER BY create_time DESC 添加索引。
  4. 分页优化

    • 避免 LIMIT 100000, 10(跳过 10 万行),改用:
    SELECT * FROM orders 
    WHERE id > 100000  -- 基于有序唯一键
    ORDER BY id LIMIT 10;
    

特殊场景执行顺序

窗口函数(Window Functions)
SELECT 
    name,
    salary,
    RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rank  -- 在 WHERE 后执行
FROM employees
WHERE status = 'active';
  • 执行时机
    窗口函数在 WHERE 之后、GROUP BY 之前执行(但晚于普通 SELECT 表达式)。
子查询
  • FROM 子查询:优先执行(作为数据源)。
  • WHERE 子查询:每行过滤时执行(可能成为性能瓶颈)。
    SELECT * FROM products
    WHERE price > (SELECT AVG(price) FROM products); -- 对每行执行一次
    

总结口诀

饭(FROM)后(ON)喂(WHERE)狗(GROUP BY)
还(HAVING)要(SELECT)去(DISTINCT)欧(ORDER BY)浪(LIMIT)

💡 终极忠告
掌握执行顺序 = 掌握 SQL 性能优化的钥匙!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值