当前位置: 首页 > news >正文

SQL执行过程及原理详解

SQL执行过程及原理详解

从入门到精通:理解关系型数据库SQL代码的执行机制与实例分析

一、 SQL执行概述

​ SQL(结构化查询语言)是与关系型数据库交互的标准语言,但很多初学者常常困惑于为什么看似正确的SQL语句会返回错误结果或执行效率低下。这往往是因为不理解SQL的执行顺序与书写顺序的差异

  • SQL执行的"错位"逻辑

​ 当你从SELECT开始写查询时,数据库可能已经完成了FROM、JOIN、WHERE等操作。这种"先备料后烹饪"的逻辑,就像做蛋炒饭需要先准备食材,再翻炒,最后盛盘,而不是先盛盘再做饭。掌握这种执行顺序是成为SQL高手的第一步

  • 书写顺序 vs 执行顺序

​ 大多数SQL初学者按照书写顺序思考:SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY,但数据库的实际执行顺序完全不同。

书写顺序执行顺序说明
SELECTFROM首先确定数据来源
FROMJOIN & ON关联表并应用连接条件
JOINWHERE过滤行数据
WHEREGROUP BY数据分组
GROUP BYHAVING过滤分组结果
HAVINGSELECT选择要显示的列
ORDER BYORDER BY结果排序
LIMITLIMIT限制结果数量

​ 理解这种执行顺序差异,能帮助你避免常见的SQL错误,写出更高效的查询语句。接下来,我们将详细解析SQL执行的每个步骤及其原理。

二、SQL执行的详细步骤

当一条SQL语句发送到数据库后,会经过一系列复杂的处理流程。虽然不同数据库(如MySQL、PostgreSQL、Oracle)在实现上略有差异,但基本遵循相似的执行步骤。

1、查询处理管线

以PostgreSQL为例,SQL查询会经过以下处理阶段:

  • 解析器 (Parser)

​ 对SQL查询字符串进行词法分析和语法分析,检查语法是否正确,并生成一个解析树(Parse Tree)

  • 分析器/语义分析器

​ 对解析树进行语义检查,验证表、列是否存在,数据类型是否匹配,权限是否满足等,并生成查询树(Query Tree)

  • 重写器/规则系统

​ 应用数据库中定义的任何规则(例如视图展开)来转换查询树

  • 优化器/规划器

​ 生成多种可能的执行计划,并根据成本估算模型选择一个预计执行效率最高的计划

  • 执行器 (Executor)

​ 接收优化器选择的最佳执行计划,并按照计划中的步骤执行操作,返回结果

2、SQL语句执行步骤

以常见的SELECT查询为例,我们详细解析其执行步骤:

  • 示例SQL查询

以下面这条SQL为例,我们将跟踪其完整执行过程:

selectdepartment,COUNT(*) as employee_count
from users
where status = 'active'
group by department
having COUNT(*) > 10
order by employee_count desc
①. FROM:确定数据源

执行的第一步是处理FROM子句,确定数据来源表。如果涉及多表连接,会先执行笛卡尔积,生成虚拟表VT1

②. JOIN 和 ON:表连接与条件过滤

如果查询涉及多表连接(如INNER JOIN、LEFT JOIN等),数据库会根据ON子句中指定的条件进行连接操作,过滤不符合条件的数据,生成虚拟表VT2

⚠️ 左连接的常见陷阱

在left join中,如果将右表条件放在WHERE子句中,会过滤掉左表数据。正确做法是将右表条件放在ON子句中:

-- 错误:
selecta.name,b.order_id
from users a
left join orders b on a.id = b.user_id
where b.status = 'paid'
正确:
selecta.name,b.order_id
from users a
left join orders b 
on a.id = b.user_id and b.status = 'paid'
③. WHERE:行级过滤

对前一步得到的结果进行行级过滤,只保留符合WHERE条件的记录,生成虚拟表VT4
注意:WHERE子句不能使用聚合函数,因为此时还未进行分组操作。例如下面的写法是错误的:

-- ❌ 错误写法
selectname,COUNT(*) as cnt
from users
where COUNT(*) > 5; -- WHERE不能使用聚合函数
-- ✅ 正确写法
selectname,COUNT(*) as cnt
from users
group by name
having COUNT(*) > 5; -- 用HAVING过滤分组
④. GROUP BY:数据分组

按GROUP BY子句中的列列表对VT4中的行进行分组,为聚合函数做准备,生成虚拟表VT5

⑤. HAVING:分组过滤

HAVING子句用于对分组后的结果进行过滤,可以使用聚合函数,生成虚拟表VT6

⑥. SELECT:选择列

到这一步,数据库才开始处理SELECT子句,选择要显示的列。这就是为什么WHERE子句不能使用SELECT中定义的别名,因为WHERE执行在SELECT之前

-- ❌ 错误:WHERE执行在SELECT之前
selectname,age * 2 as double_age
from users
where double_age > 50; -- double_age还不存在!
-- ✅ 正确写法
selectname,age * 2 as double_age
from users
where age * 2 > 50;
⑦. ORDER BY:排序

对最终结果进行排序,可以使用SELECT中定义的别名,因为ORDER BY是在SELECT之后执行的

⑧. LIMIT:限制结果数量

最后一步,限制返回的结果数量,常用于分页查询

3、MySQL执行流程示例

以MySQL为例,完整的查询执行流程如下:

  • 客户端连接

​ 用户通过客户端(如命令行工具、图形界面工具或应用程序)连接到MySQL服务器

  • 发送查询

​ 用户输入并发送SQL查询到服务器

  • 语法解析

​ 服务器检查SQL语句的语法是否正确

  • 语义解析

​ 服务器检查涉及的表和列是否存在,用户是否有权限查询这些表

  • 优化器选择执行计划

​ 优化器根据查询语句和数据库元数据(如表的统计信息、索引等)生成并选择最优执行计划

  • 权限检查

​ 服务器检查用户是否有执行该查询的权限

  • 执行查询

​ 执行器根据执行计划执行查询,包括从磁盘读取数据、使用索引进行查找、执行连接操作等

  • 返回结果

​ 查询结果被返回给客户端

  • 资源清理

​ 查询完成后,释放所有在执行过程中使用的资源

三、执行计划与优化

数据库优化器是SQL执行的核心组件,它负责为查询选择最优执行计划。理解执行计划可以帮助我们优化SQL性能,避免常见的性能问题。

1、使用EXPLAIN查看执行计划

大多数关系型数据库都提供了EXPLAIN命令,用于查看SQL语句的执行计划。以MySQL为例:

-- 查看执行计划
EXPLAIN
selectdepartment,COUNT(*) as employee_count
from users
where status = 'active'
group by department
having COUNT(*) > 10
order by employee_count desc;-- 执行并分析实际性能
EXPLAIN ANALYZE
select...;
执行计划关键参数
参数说明
type访问类型,常见值有:ALL(全表扫描)、ref(索引查找)、range(范围扫描)、const(常量查找)等。ALL表示全表扫描,性能最差
possible_keys可能使用的索引列表
key实际使用的索引,如果为NULL表示没有使用索引
rows预估需要扫描的行数,值越小越好
Extra额外信息,如"Using index"(使用覆盖索引)、“Using filesort”(文件排序)、“Using temporary”(使用临时表)等
EXPLAIN输出示例
*************************** 1. row ***************************          id: 1  select_type: SIMPLE        table: t1   partitions: NULL        type: refpossible_keys: idx_r1,idx_u1          key: idx_u1      key_len: 5          ref: const         rows: 29324     filtered: 100.00       Extra: NULL

上面的执行计划显示:SQL使用了idx_u1索引,预计扫描29324行数据

2、SQL性能优化技巧

  • 尽早过滤数据

​ WHERE子句优先写高选择性条件(如status=‘active’),减少后续处理量

  • 小表驱动大表

​ FROM子句中数据量小的表放后面(如FROM big_table JOIN small_table)

  • 避免不必要的排序

​ 如果不需要排序,不要使用ORDER BY,排序操作会增加性能开销

  • 使用合适的索引

​ 为WHERE、JOIN、ORDER BY涉及的列创建索引,但避免过度索引

3、实战案例:多表连接查询优化

假设我们有客户、订单、订单项和产品表,需要查询消费超过500的客户信息及其订单详情:

selectc.customer_id,c.name as customer_name,extract(year from o.order_date) as order_year,COUNT(o.order_id) as total_orders,SUM(o.total_amount) as total_spent,COUNT(oi.order_item_id) as total_order_items
from customers c
join orders o on c.customer_id = o.customer_id
join order_items oi on o.order_id = oi.order_id
group by c.customer_id, c.name, extract(year from o.order_date)
having SUM(o.total_amount) > 500
order by total_spent desc;

优化建议:

  • 为连接字段创建索引:customers(customer_id)、orders(customer_id, order_id)、order_items(order_id)
  • 如果只需要特定年份的数据,在WHERE子句中添加年份过滤条件,减少数据量
  • 考虑是否需要所有聚合字段,移除不必要的计算
  • 如果经常执行此查询,考虑创建物化视图

四、常见错误与解决方案

理解SQL执行顺序后,我们可以避免许多常见错误。以下是初学者最容易犯的错误及解决方法:

错误1:在WHERE中使用聚合函数

WHERE子句在分组前执行,此时聚合函数还不可用。应该使用HAVING子句过滤分组结果。

-- 错误写法
selectdepartment,AVG(salary)
from employees
where AVG(salary) > 5000 -- WHERE不能使用聚合函数
group by department;
-- 正确写法
selectdepartment,AVG(salary) as avg_salary
from employees
group by department
having avg_salary > 5000; -- HAVING在分组后执行

错误2:在WHERE中使用SELECT别名

SELECT子句在WHERE之后执行,因此WHERE中不能使用SELECT中定义的别名。

错误写法
selectname,salary * 12 as annual_salary
from employees
where annual_salary > 100000; -- annual_salary还不存在!
正确写法
selectname,salary * 12 as annual_salary
from employees
where salary * 12 > 100000;
-- 使用原始表达式
-- 或者 SELECT * FROM ( SELECT name, salary * 12 as annual_salary  FROM employees ) AS sub WHERE annual_salary > 100000;

错误3:GROUP BY与SELECT列不匹配

SELECT子句中的非聚合列必须全部出现在GROUP BY子句中,除非它们被聚合函数处理。

-- 错误写法
selectdepartment,name,COUNT(*)
from users
group by department; -- name列未在GROUP BY中,也未被聚合
-- 正确写法
-- 方案1:将name列加入GROUP BY
selectdepartment,name,COUNT(*)
from users
group by department, name;-- 方案2:对name列使用聚合函数
selectdepartment,MAX(name),COUNT(*)
from users
group by department;

错误4:LEFT JOIN条件放错位置

在LEFT JOIN中,右表的过滤条件应放在ON子句中,而非WHERE子句,否则会过滤掉左表数据。

错误写法
selecta.name,b.order_id
from users a
left join orders b on a.id = b.user_id
where b.status = 'paid'; -- 右表条件放WHERE会过滤左表数据
正确写法
selecta.name,b.order_id
from users a
left join orders b
on a.id = b.user_id and b.status = 'paid'; -- 右表条件放ON子句

错误5:过度使用SELECT *

SELECT *会返回所有列,不仅增加数据传输量,还可能导致无法使用覆盖索引,降低查询性能。

最佳实践

明确指定需要的列,避免使用SELECT *:

-- ❌ 不推荐
SELECT * FROM products WHERE category = 'Electronics';-- ✅ 推荐
SELECT product_id, product_name, price 
FROM products WHERE category = 'Electronics';

五、SQL执行顺序速记口诀

记住这个口诀,帮助你理解和记忆SQL执行顺序:

执行顺序口诀

“FROM找数据,JOIN联表,WHERE过滤,GROUP BY分组,HAVING筛组,SELECT取值,ORDER BY排序,LIMIT限制”

掌握SQL执行顺序和原理,不仅能帮助你避免常见错误,还能让你写出更高效的查询语句。记住:SQL的执行顺序与书写顺序不同,理解这种差异是成为SQL高手的关键一步。

http://www.dtcms.com/a/361693.html

相关文章:

  • [SWPUCTF 2018]SimplePHP
  • 实现自己的AI视频监控系统-第三章-信息的推送与共享2
  • 刘洋洋《一笔相思绘红妆》上线,献给当代痴心人的一封情书
  • 互斥量(Mutex,全称 Mutual Exclusion)用于保证同一时间只有一个线程(或进程)访问共享资源,从而避免并发操作导致的数据不一致问题
  • RAG-文本到SQL
  • SOME/IP-SD中IPv4端点选项与IPv4 SD端点选项
  • 突破超强回归模型,高斯过程回归!
  • 使用 BayesFlow 神经网络简化贝叶斯推断的案例分享(二)
  • 无重复字符的最长子串,leetCode热题100,C++实现
  • 【FireCrawl】:本地部署AI爬虫+DIFY集成+V2新特性
  • FFmpeg 不同编码的压缩命令详解
  • 速卖通自养号测评系统开发指南:环境隔离与行为模拟实战
  • 测试-用例篇
  • FFMPEG AAC
  • 【LeetCode每日一题】19. 删除链表的倒数第 N 个结点 24. 两两交换链表中的节点
  • Java内存模型下的高性能锁优化与无锁编程实践指南
  • 几种特殊的数字滤波器---原理及设计
  • 【零碎小知识点 】(四) Java多线程编程深入与实践
  • MongoDB主从切换实战:如何让指定从库“精准”升级为主库?保姆级教程!
  • 36. Ansible变量+管理机密
  • 【Android】使用Handler做多个线程之间的通信
  • Java面试宝典:Redis高并发高可用(集群)
  • 函数,数组与正则表达式
  • Kafka 架构原理
  • 销售事业十年规划,并附上一套能帮助销售成长的「软件工具组合」
  • 【git 基础】detached HEAD state的出现和解决
  • C++11模板优化大揭秘:让你的代码更简洁、更安全、更高效
  • javaScript变量命名规则
  • 【汇客项目】:在启动过程中报错 本来安装的是node-sass 被卸载后安装的sass ,代码中一部分出现问题
  • 【深度学习基础】深度学习中的数据增强技术:从理论到实践的解析