当前位置: 首页 > 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.xdnf.cn/news/1421659.html

相关文章:

  • AI + 机器人:当大语言模型赋予机械 “思考能力”,未来工厂将迎来怎样变革?
  • 三、SVN实践练习指南
  • 轻量级注意力模型HOTSPOT-YOLO:无人机光伏热异常检测新SOTA,mAP高达90.8%
  • Swift 解法详解:LeetCode 368《最大整除子集》
  • 【牛客JZ31】—栈的压入弹出序列判断算法详解
  • FPGA中的亚稳态与跨时钟域数据撕裂现象
  • 眼底病害图像分类数据集
  • MYSQL速通(4/5)
  • KL Loss
  • Python OpenCV图像处理与深度学习:Python OpenCV图像滤波入门
  • [系统架构设计师]论文(二十三)
  • 基于SpringBoot+MYSQL开发的师生成果管理系统
  • 美术馆预约小程序|基于微信小程序的美术馆预约平台设计与实现(源码+数据库+文档)
  • zotero.sqlite已损坏
  • 第9篇:监控与运维 - 集成Actuator健康检查
  • 『C++成长记』vector模拟实现
  • 车载总线架构 --- 车载LIN总线传输层概述
  • 百胜软件获邀出席第七届中国智慧零售大会,智能中台助力品牌零售数智变革
  • C++ 虚继承:破解菱形继承的“双亲困境”
  • 【macOS】垃圾箱中文件无法清理的--特殊方法
  • Linux | 走进网络世界:MAC、IP 与通信的那些事
  • PyTorch 实战(3)—— PyTorch vs. TensorFlow:深度学习框架的王者之争
  • mysql中如何解析某个字段是否是中文
  • 攻防演练笔记
  • Frida Hook API 转换/显示堆栈
  • 【数学建模学习笔记】缺失值处理
  • 数学分析原理答案——第七章 习题13
  • 文件夹上传 (UploadFolder)
  • crypto-babyrsa(2025YC行业赛)
  • 【系统架构师设计(8)】需求分析之 SysML系统建模语言:从软件工程到系统工程的跨越