💫《博主主页》:
🔎 CSDN主页__奈斯DB
🔎 IF Club社区主页__奈斯、
🔥《擅长领域》:擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控;并对SQLserver、NoSQL(Redis)有了解
💖如果觉得文章对你有所帮助,欢迎点赞收藏加关注💖
在博主之前的文章中有详细介绍过SQL的 执行过程 ,那么举一反三,各位有没有考虑过SQL的 执行顺序 又是什么样子的呢?有些大佬可能对SQL的执行顺序熟记于心,有些小伙伴可能还不是很了解,那么今天这篇文章就来系统性地解析SQL语句执行顺序这个关键问题!🎯
就像餐厅点餐和厨师做菜的流程往往不同,SQL的书写顺序和执行顺序也存在着有趣的差异🎭。当我们写SQL查询时,通常按这样的逻辑顺序:
1.SELECT – 先写要什么(就像先点餐)
2.FROM – 从哪里获取
3.WHERE – 筛选条件
4.GROUP BY – 如何分组
5.HAVING – 分组后筛选
但数据库引擎实际执行的顺序却是这样的🤯:
1.准备食材(FROM/JOIN): 先确定数据来源,就像厨师先准备原材料
2.筛选优质原料(WHERE): 过滤掉不符合条件的记录,相当于剔除不新鲜的食材
3.分类处理(GROUP BY): 对数据进行分组,就像把食材按类别分开
4.成品质检(HAVING): 对分组后的结果进行筛选,好比检查每道菜的质量
5.最终呈现(SELECT): 最后才决定输出哪些列,就像摆盘上菜
如上只是简单的对select进行举例,那么关于更详细的执行顺序,将在下面的文章中逐一进行介绍。那么各位大佬继续跟随我的视角,开始今天的文章。💥
这里需要区分两个概念,那就是 执行流程 和 执行顺序 。执行顺序 是SQL标准定义的 逻辑处理步骤(如先WHERE后GROUP BY),决定结果如何计算;执行流程 是Oracle实际的 物理处理过程(如先走索引再回表),决定如何高效执行。那么为了区分这两个概念,我将分为两篇文章介绍,两篇文章分别如下:
- 第一篇:一条 SQL 语句的执行流程(含优化器详解)
- 第二篇:一条 SQL 语句的执行顺序(含DQL和DML)(当前篇)
一、select语句执行顺序
SELECT语句语法结构(列出了重点,完整参考官方文档):
官方文档对于select语句的介绍(Oracle 12c):SELECT[WITH [plsql_declarations] subquery_factoring_clause [, subquery_factoring_clause]... ] SELECT [hint] [DISTINCT | UNIQUE | ALL] select_list FROM table_reference [, table_reference | join_clause]... [WHERE condition] [hierarchical_query_clause] [GROUP BY group_by_expression] [HAVING condition] [model_clause] [order_by_clause] [row_limiting_clause] [for_update_clause];
如上是select语句的语法,也是标准书写顺序,那么真实的执行顺序如下:
- WITH子句(预处理阶段)
- 功能: 定义临时命名查询块(子查询或 PL/SQL 函数),供后续查询引用。
- 执行逻辑:
- 先解析
plsql_declarations
(若有),声明 PL/SQL 函数或变量。- 再执行
subquery_factoring_clause
中的子查询,结果存储在内存中(类似临时表)。- 子查询中的ORDER BY子句会被忽略(除非配合
LIMIT/OFFSET
),因为优化器可能重排数据。- 文档依据: Oracle 官方文档明确指出 WITH 子句在主查询执行前处理。
- FROM子句(数据源处理)
- 功能: 定义查询的数据源(表、视图、子查询)及连接方式。
- 执行逻辑:
- 按顺序解析每个
table_reference
(表、视图、子查询),包括:
- 处理闪回查询(
AS OF TIMESTAMP
)获取历史数据版本。- 执行采样(
SAMPLE
)减少数据量。- 展开内联视图(
WITH
子句或子查询)。- 执行连接操作(
JOIN
):
- 优化器可能调整连接顺序(如小表驱动大表),但逻辑上仍遵循FROM的依赖关系。
- 先处理
NATURAL JOIN
和USING
子句,再处理ON
条件。- 文档依据: 连接操作属于
FROM
子句的一部分,且在WHERE
过滤前执行。- WHERE子句(行过滤)
- 功能: 过滤
FROM
子句生成的结果集,仅保留满足条件的行。- 执行逻辑:
- 先处理与连接相关的条件(如
JOIN ... ON
中的条件),属于FROM子句的一部分。- 再处理其他过滤条件(如
column > value
),可能涉及函数或子查询。- 若包含分层查询(
CONNECT BY
),其条件在WHERE其他条件之后、分组之前执行。- 优化器行为:
- 提前过滤数据(如通过索引快速排除不满足条件的行),减少后续处理的数据量。
- 将过滤条件下推到数据源(如分区过滤
PARTITION
)。- 分层查询子句(hierarchical_query_clause)
- 功能: 基于
CONNECT BY
和START WITH
生成分层结果集(如组织架构)。- 执行逻辑:
- 先通过
START WITH
确定根节点。- 再通过
CONNECT BY
递归生成子节点,形成层级结构。PRIOR
操作符用于引用父节点值,在递归过程中生效。- 执行位置: 在
WHERE
基本过滤后、GROUP BY
之前执行,因为分层结构影响分组逻辑。- GROUP BY子句(分组与聚合)
- 功能: 将行按指定列或表达式分组,配合聚合函数(如
SUM、COUNT
)计算汇总值。- 执行逻辑:
- 按
GROUP BY
后的列 / 表达式对过滤后的行进行分组。- 对每个分组应用聚合函数,生成汇总行。
- 限制:
SELECT
列表中只能包含分组列、聚合函数或常量,否则会报错(ORA-00979
)。- HAVING子句(分组过滤)
- 功能: 过滤
GROUP BY
生成的分组结果,仅保留满足条件的分组。- 执行逻辑:
- 作用于分组后的汇总行,可使用聚合函数(如
HAVING SUM(salary) > 10000
)。- 执行顺序严格在
GROUP BY
之后,因为依赖分组结果。- SELECT列表(列投影与去重)
- 功能: 定义最终返回的列或表达式,包括去重(
DISTINCT/UNIQUE
)操作。- 执行逻辑:
- 计算
SELECT
列表中的表达式(如column + 10
、函数调用)。- 若包含
DISTINCT/UNIQUE
,则基于 SELECT 列表的所有列去除重复行。- 限制:
DISTINCT
不能与GROUP BY
同时使用(语义冲突)。- model(多维数据分析子句)
- 功能: 多维模型计算,可能更新或生成新列
- 执行逻辑:
- 将结果集转换为多维模型。
- 按规则更新单元格值(可能涉及迭代计算)。
- 生成最终的模型结果。
- ORDER BY子句(排序)
- 功能: 对查询结果进行排序,是影响结果集顺序的最后一步操作。
- 执行逻辑:
- 基于指定的列、表达式或位置(如
ORDER BY 1 DESC
)对结果集排序。- 若包含
SIBLINGS
关键字(配合分层查询),则先保留分层结构,再对同级行排序。- 特点:
ORDER BY
不改变数据本身,仅影响返回顺序,且是唯一能保证结果顺序的子句。- row_limiting_clause(行限制)
- 功能: 限制返回的行数(如
FETCH FIRST 10 ROWS ONLY
),依赖ORDER BY
的排序结果。- 执行逻辑:
- 在
ORDER BY
之后执行,根据排序后的结果跳过指定行数(OFFSET
)并返回限制行数(FETCH
)。- 若指定
WITH TIES
,则包含与最后一行排序键相同的额外行。- for_update_clause(行锁定)
- 功能: 锁定查询返回的行,防止其他事务修改,是查询执行的最后一步。
- 执行逻辑:
- 在结果集确定后、返回给用户前锁定行,锁定范围由
OF column
指定(默认锁定所有表的行)。- 可配合
WAIT/NOWAIT/SKIP LOCKED
控制锁定行为(如FOR UPDATE WAIT 5
表示等待 5 秒获取锁)。
那么对比一下手写的SQL和SQL实际在数据库层面的读取顺序:
二、update语句执行顺序
UPDATE语句语法结构(列出了重点,完整参考官方文档):
官方文档对于update语句的介绍(Oracle 12c):UPDATEUPDATE [hint] {table|view} [alias] SET {column = {expression | subquery} | (column [, column]...) = (subquery)} [, {column = {expression | subquery} | (column [, column]...) = (subquery)}]... [WHERE condition] [RETURNING {expression [into_clause] | {* | expression [, expression]...} INTO host_variable_list}];
如上是update语句的语法,也是标准书写顺序,那么真实的执行顺序如下:
- 表锁定与访问阶段
- 功能: 锁定目标表,准备数据访问。
- 执行逻辑:
- 对UPDATE语句中指定的表(
{table|view}
)加行级锁(ROW SHARE
)或表级锁(取决于hint
)。- 解析表的结构(如列名、数据类型),验证
SET
子句中的列是否存在。- 优化器行为:
- 若存在索引可快速定位待更新的行,优化器会优先使用索引。
- 对视图更新时,会先展开视图为底层表的操作。
- SET子句执行阶段
- 功能: 计算并设置列的新值。
- 执行逻辑:
- 按SET子句中列的顺序,依次计算每个赋值表达式:
- 若表达式为常量(如
column = 10
),直接赋值。- 若表达式为子查询,执行子查询并获取结果。
- 若表达式依赖其他列(如
column = column * 2
),使用更新前的值计算(Oracle 不保证多列更新的顺序)。- 临时存储所有列的新值,但不立即写入磁盘(需等待事务提交)。
- 限制:
- 不能在同一UPDATE中多次更新同一列(如
SET col = 1, col = 2
)。- 子查询必须返回单行单列(除非使用多行子查询配合ROWID)。
- WHERE子句过滤阶段
- 功能: 确定哪些行需要更新。
- 执行逻辑:
- 对目标表中的每一行(或通过索引快速定位的行),评估
WHERE
条件。- 仅对满足条件的行应用SET子句中的赋值操作。
- 优化器行为:
- 提前过滤数据(如通过索引快速排除不满足条件的行),减少更新的行数。
- 将
WHERE
条件下推到关联表(若更新多表视图)。- 数据验证与约束检查阶段
- 功能: 确保更新后的数据符合表定义的约束。
- 执行逻辑:
- 检查非空约束(
NOT NULL
):若更新后列值为NULL且列定义为非空,抛出错误。- 检查唯一约束(
UNIQUE
):确保更新后的列值在表中唯一。- 检查外键约束(
FOREIGN KEY
):若更新父表的主键,确保子表关联行存在。- 检查检查约束(
CHECK
):确保更新后的列值满足自定义条件。- 执行时机: 在
WHERE
过滤后、实际更新前执行,确保数据合法性。- 行更新与回滚段记录阶段
- 功能: 执行实际的行更新,并记录回滚信息。
- 执行逻辑:
- 对满足WHERE条件的行,将SET子句计算的新值写入数据块。
- 在回滚段(
Undo Segment
)中记录旧值,用于事务回滚或读一致性。- 更新数据块的头部信息(如时间戳、行版本号)。
- 特性:
- 原子性:每行更新要么全部完成,要么全部回滚。
- 多版本一致性:其他事务仍可读取更新前的数据。
- RETURNING子句返回阶段
- 功能: 返回更新后的行数据(或表达式结果)。
- 执行逻辑:
- 对每一行更新后的数据,计算
RETURNING
子句中的表达式。- 将结果返回给客户端(或存储到
INTO
子句指定的变量中)。- 注意:
RETURNING
子句返回的是更新后的最新值,可用于获取自增列或计算值。
那么对比一下手写的SQL和SQL实际在数据库层面的读取顺序:
三、delete语句执行顺序
DELETE语句语法结构(列出了重点,完整参考官方文档):
官方文档对于delete 语句的介绍(Oracle 12c)DELETEDELETE [hint] FROM {table|view} [alias] [WHERE condition] [RETURNING {expression [into_clause] | {* | expression [, expression]...} INTO host_variable_list}];
如上是delete语句的语法,也是标准书写顺序,那么真实的执行顺序如下:
- 表锁定与访问阶段
- 功能: 锁定目标表,准备数据访问。
- 执行逻辑:
- 对DELETE语句中指定的表(
{table|view}
)加行级锁(ROW SHARE
)或表级锁(取决于hint
)。- 解析表的结构(如列名、主键),验证删除权限。
- 若删除目标是视图,展开视图为底层表的连接操作(需满足可删除条件)。
- 优化器行为:
- 若存在索引可快速定位待删除的行,优化器会优先使用索引。
- WHERE子句过滤阶段
- 功能: 确定哪些行需要删除。
- 执行逻辑:
- 对目标表中的每一行(或通过索引快速定位的行),评估WHERE条件。
- 仅对满足条件的行标记为待删除。
- 优化器行为:
- 提前过滤数据(如通过索引快速排除不满足条件的行),减少扫描的数据量。
- 将WHERE条件下推到关联表(若删除多表视图)。
- 约束检查阶段
- 功能: 确保删除操作不违反表间约束。
- 执行逻辑:
- 检查外键约束(
FOREIGN KEY
):若删除父表的主键,确保子表无关联行(或设置为ON DELETE CASCADE
)。- 检查引用约束:确保删除操作不会导致其他表的REF列悬空。
- 执行时机: 在物理删除前执行,确保数据完整性。
- 物理删除阶段
- 功能: 执行实际的行删除,并记录回滚信息。
- 执行逻辑:
- 对满足WHERE条件的行,从数据块中标记为 “已删除”(非立即物理清除)。
- 在回滚段(
Undo Segment
)中记录旧值,用于事务回滚或读一致性。- 更新数据块的头部信息(如空闲空间、行目录)。
- 特性:
- 原子性:每行删除要么全部完成,要么全部回滚。
- 多版本一致性:其他事务仍可读取删除前的数据。
- RETURNING子句返回阶段
- 功能: 返回被删除的行数据(或表达式结果)。
- 执行逻辑:
- 对每一行删除前的数据,计算
RETURNING
子句中的表达式。- 将结果返回给客户端(或存储到
INTO
子句指定的变量中)。
那么对比一下手写的SQL和SQL实际在数据库层面的读取顺序:
四、insert语句执行顺序
INSERT语句语法结构(列出了重点,完整参考官方文档):
官方文档对于insert 语句的介绍(Oracle 12c):INSERT
INSERT [hint] { INTO table [ (column [, column]...) ] { VALUES (expr [, expr]...) | subquery } | INTO [schema.]table@dblink [ (column [, column]...) ] { VALUES (expr [, expr]...) | subquery } | FIRST | ALL [WHEN condition THEN] INSERT INTO table [ (column [, column]...) ] { VALUES (expr [, expr]...) | subquery } [WHEN condition THEN] INSERT INTO table [ (column [, column]...) ] { VALUES (expr [, expr]...) | subquery } [...] [ELSE INSERT INTO table [ (column [, column]...) ] { VALUES (expr [, expr]...) | subquery }] } [RETURNING {expression [into_clause] | {* | expression [, expression]...} INTO host_variable_list}];
如上是insert语句的语法,也是标准书写顺序,那么真实的执行顺序如下:
- 表锁定与解析阶段
- 功能: 锁定目标表,解析表结构和列引用。
- 执行逻辑:
- 对目标表加行级锁(
ROW SHARE
)或表级锁(取决于hint
)。- 验证表存在性,解析列名和数据类型。
- 若插入通过视图,展开视图为底层表的操作(需满足可插入条件)。
- 值准备阶段
- 功能: 计算待插入的值。
- 执行逻辑:
- VALUES 子句:
- 按顺序计算每个
expr
的值(如常量、函数调用、变量)。- 隐式类型转换(如字符串转为数字)。
- 子查询方式:
- 执行
subquery
,获取结果集。- 验证结果集的列数和数据类型与目标表匹配。
- 约束验证阶段
- 功能: 确保插入的数据符合表定义的约束。
- 执行逻辑:
- 检查非空约束(
NOT NULL
):若未显式提供值且列定义为非空,使用默认值(若有)或抛出错误。- 检查唯一约束(
UNIQUE
):确保插入的值在表中唯一。- 检查外键约束(
FOREIGN KEY
):确保引用的父键存在。- 检查检查约束(
CHECK
):确保值满足自定义条件。- 物理插入阶段
- 功能: 将数据写入数据块。
- 执行逻辑:
- 为新行分配空间(从数据块的空闲空间中获取)。
- 将计算好的值按列顺序写入数据块。
- 更新数据块的头部信息(如行目录、空闲空间指针)。
- 在回滚段(
Undo Segment
)中记录插入操作,用于事务回滚。- 索引更新阶段
- 功能: 更新与插入列相关的索引。
- 执行逻辑:
- 对表上的每个索引(主键、唯一索引、非唯一索引):
- 提取索引列的值。
- 将索引项插入到索引结构中(如 B 树)。
- RETURNING子句返回阶段
- 功能: 返回插入的行数据(或表达式结果)。
- 执行逻辑:
- 对每一行插入后的数据,计算
RETURNING
子句中的表达式。- 将结果返回给客户端(或存储到
INTO
子句指定的变量中)。
那么对比一下手写的SQL和SQL实际在数据库层面的读取顺序:
最后总结一下,记住这个核心要点🎯:SQL语句的 书写顺序≠执行顺序 。就像做菜要先备料再烹饪一样,数据库总是按照它自己的逻辑流程来处理你的查询。