【Oracle篇】一条 SQL 语句的执行顺序(含DQL和DML)(第二篇,总共两篇)

发布于:2025-07-31 ⋅ 阅读:(17) ⋅ 点赞:(0)

💫《博主主页》:
   🔎 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语句的语法,也是标准书写顺序,那么真实的执行顺序如下:

  1. WITH子句(预处理阶段)
    • 功能: 定义临时命名查询块(子查询或 PL/SQL 函数),供后续查询引用。
    • 执行逻辑:
      1. 先解析plsql_declarations(若有),声明 PL/SQL 函数或变量。
      2. 再执行subquery_factoring_clause中的子查询,结果存储在内存中(类似临时表)。
      3. 子查询中的ORDER BY子句会被忽略(除非配合LIMIT/OFFSET),因为优化器可能重排数据。
    • 文档依据: Oracle 官方文档明确指出 WITH 子句在主查询执行前处理。
  2. FROM子句(数据源处理)
    • 功能: 定义查询的数据源(表、视图、子查询)及连接方式。
    • 执行逻辑:
      1. 按顺序解析每个table_reference(表、视图、子查询),包括:
        • 处理闪回查询(AS OF TIMESTAMP)获取历史数据版本。
        • 执行采样(SAMPLE)减少数据量。
        • 展开内联视图(WITH子句或子查询)。
      2. 执行连接操作(JOIN):
        • 优化器可能调整连接顺序(如小表驱动大表),但逻辑上仍遵循FROM的依赖关系。
        • 先处理NATURAL JOINUSING子句,再处理ON条件。
    • 文档依据: 连接操作属于FROM子句的一部分,且在 WHERE过滤前执行。
  3. WHERE子句(行过滤)
    • 功能: 过滤FROM子句生成的结果集,仅保留满足条件的行。
    • 执行逻辑:
      1. 先处理与连接相关的条件(如JOIN ... ON中的条件),属于FROM子句的一部分。
      2. 再处理其他过滤条件(如column > value),可能涉及函数或子查询。
      3. 若包含分层查询(CONNECT BY),其条件在WHERE其他条件之后、分组之前执行。
    • 优化器行为:
      • 提前过滤数据(如通过索引快速排除不满足条件的行),减少后续处理的数据量。
      • 将过滤条件下推到数据源(如分区过滤PARTITION)。
  4. 分层查询子句(hierarchical_query_clause)
    • 功能: 基于CONNECT BYSTART WITH生成分层结果集(如组织架构)。
    • 执行逻辑:
      1. 先通过START WITH确定根节点。
      2. 再通过CONNECT BY递归生成子节点,形成层级结构。
      3. PRIOR操作符用于引用父节点值,在递归过程中生效。
    • 执行位置:WHERE基本过滤后、GROUP BY之前执行,因为分层结构影响分组逻辑。
  5. GROUP BY子句(分组与聚合)
    • 功能: 将行按指定列或表达式分组,配合聚合函数(如 SUM、COUNT)计算汇总值。
    • 执行逻辑:
      1. GROUP BY后的列 / 表达式对过滤后的行进行分组。
      2. 对每个分组应用聚合函数,生成汇总行。
    • 限制:SELECT列表中只能包含分组列、聚合函数或常量,否则会报错(ORA-00979)。
  6. HAVING子句(分组过滤)
    • 功能: 过滤GROUP BY生成的分组结果,仅保留满足条件的分组。
    • 执行逻辑:
      1. 作用于分组后的汇总行,可使用聚合函数(如HAVING SUM(salary) > 10000)。
      2. 执行顺序严格在GROUP BY之后,因为依赖分组结果。
  7. SELECT列表(列投影与去重)
    • 功能: 定义最终返回的列或表达式,包括去重(DISTINCT/UNIQUE)操作。
    • 执行逻辑:
      1. 计算SELECT列表中的表达式(如column + 10、函数调用)。
      2. 若包含DISTINCT/UNIQUE,则基于 SELECT 列表的所有列去除重复行。
    • 限制:DISTINCT不能与GROUP BY同时使用(语义冲突)。
  8. model(多维数据分析子句)
    • 功能: 多维模型计算,可能更新或生成新列
    • 执行逻辑:
      1. 将结果集转换为多维模型。
      2. 按规则更新单元格值(可能涉及迭代计算)。
      3. 生成最终的模型结果。
  9. ORDER BY子句(排序)
    • 功能: 对查询结果进行排序,是影响结果集顺序的最后一步操作。
    • 执行逻辑:
      1. 基于指定的列、表达式或位置(如ORDER BY 1 DESC)对结果集排序。
      2. 若包含SIBLINGS关键字(配合分层查询),则先保留分层结构,再对同级行排序。
    • 特点:ORDER BY不改变数据本身,仅影响返回顺序,且是唯一能保证结果顺序的子句。
  10. row_limiting_clause(行限制)
    • 功能: 限制返回的行数(如FETCH FIRST 10 ROWS ONLY),依赖ORDER BY的排序结果。
    • 执行逻辑:
      1. ORDER BY之后执行,根据排序后的结果跳过指定行数(OFFSET)并返回限制行数(FETCH)。
      2. 若指定WITH TIES,则包含与最后一行排序键相同的额外行。
  11. for_update_clause(行锁定)
    • 功能: 锁定查询返回的行,防止其他事务修改,是查询执行的最后一步。
    • 执行逻辑:
      1. 在结果集确定后、返回给用户前锁定行,锁定范围由OF column指定(默认锁定所有表的行)。
      2. 可配合WAIT/NOWAIT/SKIP LOCKED控制锁定行为(如FOR UPDATE WAIT 5表示等待 5 秒获取锁)。
           
           

那么对比一下手写的SQL和SQL实际在数据库层面的读取顺序:
在这里插入图片描述


收藏点赞加关注,技术成长快一步!

在这里插入图片描述

   
   

二、update语句执行顺序

UPDATE语句语法结构(列出了重点,完整参考官方文档):
    官方文档对于update语句的介绍(Oracle 12c):UPDATE

UPDATE [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语句的语法,也是标准书写顺序,那么真实的执行顺序如下:

  1. 表锁定与访问阶段
    • 功能: 锁定目标表,准备数据访问。
    • 执行逻辑:
      1. 对UPDATE语句中指定的表({table|view})加行级锁(ROW SHARE)或表级锁(取决于hint)。
      2. 解析表的结构(如列名、数据类型),验证SET子句中的列是否存在。
    • 优化器行为:
      1. 若存在索引可快速定位待更新的行,优化器会优先使用索引。
      2. 对视图更新时,会先展开视图为底层表的操作。
  2. SET子句执行阶段
    • 功能: 计算并设置列的新值。
    • 执行逻辑:
      1. 按SET子句中列的顺序,依次计算每个赋值表达式:
        • 若表达式为常量(如column = 10),直接赋值。
        • 若表达式为子查询,执行子查询并获取结果。
        • 若表达式依赖其他列(如column = column * 2),使用更新前的值计算(Oracle 不保证多列更新的顺序)。
      2. 临时存储所有列的新值,但不立即写入磁盘(需等待事务提交)。
    • 限制:
      1. 不能在同一UPDATE中多次更新同一列(如SET col = 1, col = 2)。
      2. 子查询必须返回单行单列(除非使用多行子查询配合ROWID)。
  3. WHERE子句过滤阶段
    • 功能: 确定哪些行需要更新。
    • 执行逻辑:
      1. 对目标表中的每一行(或通过索引快速定位的行),评估WHERE条件。
      2. 仅对满足条件的行应用SET子句中的赋值操作。
    • 优化器行为:
      1. 提前过滤数据(如通过索引快速排除不满足条件的行),减少更新的行数。
      2. WHERE条件下推到关联表(若更新多表视图)。
  4. 数据验证与约束检查阶段
    • 功能: 确保更新后的数据符合表定义的约束。
    • 执行逻辑:
      1. 检查非空约束(NOT NULL):若更新后列值为NULL且列定义为非空,抛出错误。
      2. 检查唯一约束(UNIQUE):确保更新后的列值在表中唯一。
      3. 检查外键约束(FOREIGN KEY):若更新父表的主键,确保子表关联行存在。
      4. 检查检查约束(CHECK):确保更新后的列值满足自定义条件。
    • 执行时机:WHERE过滤后、实际更新前执行,确保数据合法性。
  5. 行更新与回滚段记录阶段
    • 功能: 执行实际的行更新,并记录回滚信息。
    • 执行逻辑:
      1. 对满足WHERE条件的行,将SET子句计算的新值写入数据块。
      2. 在回滚段(Undo Segment)中记录旧值,用于事务回滚或读一致性。
      3. 更新数据块的头部信息(如时间戳、行版本号)。
    • 特性:
      1. 原子性:每行更新要么全部完成,要么全部回滚。
      2. 多版本一致性:其他事务仍可读取更新前的数据。
  6. RETURNING子句返回阶段
    • 功能: 返回更新后的行数据(或表达式结果)。
    • 执行逻辑:
      1. 对每一行更新后的数据,计算RETURNING子句中的表达式。
      2. 将结果返回给客户端(或存储到INTO子句指定的变量中)。
    • 注意:
      1. RETURNING子句返回的是更新后的最新值,可用于获取自增列或计算值。
           
           

那么对比一下手写的SQL和SQL实际在数据库层面的读取顺序:
在这里插入图片描述

   
   

三、delete语句执行顺序

DELETE语句语法结构(列出了重点,完整参考官方文档):
    官方文档对于delete 语句的介绍(Oracle 12c)DELETE

DELETE [hint] 
   FROM {table|view} [alias]
   [WHERE condition]
   [RETURNING 
       {expression [into_clause]
       | {* | expression [, expression]...} INTO host_variable_list}];

   

如上是delete语句的语法,也是标准书写顺序,那么真实的执行顺序如下:

  1. 表锁定与访问阶段
    • 功能: 锁定目标表,准备数据访问。
    • 执行逻辑:
      1. 对DELETE语句中指定的表({table|view})加行级锁(ROW SHARE)或表级锁(取决于hint)。
      2. 解析表的结构(如列名、主键),验证删除权限。
      3. 若删除目标是视图,展开视图为底层表的连接操作(需满足可删除条件)。
    • 优化器行为:
      • 若存在索引可快速定位待删除的行,优化器会优先使用索引。
  2. WHERE子句过滤阶段
    • 功能: 确定哪些行需要删除。
    • 执行逻辑:
      1. 对目标表中的每一行(或通过索引快速定位的行),评估WHERE条件。
      2. 仅对满足条件的行标记为待删除。
    • 优化器行为:
      1. 提前过滤数据(如通过索引快速排除不满足条件的行),减少扫描的数据量。
      2. 将WHERE条件下推到关联表(若删除多表视图)。
  3. 约束检查阶段
    • 功能: 确保删除操作不违反表间约束。
    • 执行逻辑:
      1. 检查外键约束(FOREIGN KEY):若删除父表的主键,确保子表无关联行(或设置为ON DELETE CASCADE)。
      2. 检查引用约束:确保删除操作不会导致其他表的REF列悬空。
    • 执行时机: 在物理删除前执行,确保数据完整性。
  4. 物理删除阶段
    • 功能: 执行实际的行删除,并记录回滚信息。
    • 执行逻辑:
      1. 对满足WHERE条件的行,从数据块中标记为 “已删除”(非立即物理清除)。
      2. 在回滚段(Undo Segment)中记录旧值,用于事务回滚或读一致性。
      3. 更新数据块的头部信息(如空闲空间、行目录)。
    • 特性:
      1. 原子性:每行删除要么全部完成,要么全部回滚。
      2. 多版本一致性:其他事务仍可读取删除前的数据。
  5. RETURNING子句返回阶段
    • 功能: 返回被删除的行数据(或表达式结果)。
    • 执行逻辑:
      1. 对每一行删除前的数据,计算RETURNING子句中的表达式。
      2. 将结果返回给客户端(或存储到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语句的语法,也是标准书写顺序,那么真实的执行顺序如下:

  1. 表锁定与解析阶段
    • 功能: 锁定目标表,解析表结构和列引用。
    • 执行逻辑:
      1. 对目标表加行级锁(ROW SHARE)或表级锁(取决于hint)。
      2. 验证表存在性,解析列名和数据类型。
      3. 若插入通过视图,展开视图为底层表的操作(需满足可插入条件)。
  2. 值准备阶段
    • 功能: 计算待插入的值。
    • 执行逻辑:
      • VALUES 子句:
        1. 按顺序计算每个expr的值(如常量、函数调用、变量)。
        2. 隐式类型转换(如字符串转为数字)。
      • 子查询方式:
        1. 执行subquery,获取结果集。
        2. 验证结果集的列数和数据类型与目标表匹配。
  3. 约束验证阶段
    • 功能: 确保插入的数据符合表定义的约束。
    • 执行逻辑:
      1. 检查非空约束(NOT NULL):若未显式提供值且列定义为非空,使用默认值(若有)或抛出错误。
      2. 检查唯一约束(UNIQUE):确保插入的值在表中唯一。
      3. 检查外键约束(FOREIGN KEY):确保引用的父键存在。
      4. 检查检查约束(CHECK):确保值满足自定义条件。
  4. 物理插入阶段
    • 功能: 将数据写入数据块。
    • 执行逻辑:
      1. 为新行分配空间(从数据块的空闲空间中获取)。
      2. 将计算好的值按列顺序写入数据块。
      3. 更新数据块的头部信息(如行目录、空闲空间指针)。
      4. 在回滚段(Undo Segment)中记录插入操作,用于事务回滚。
  5. 索引更新阶段
    • 功能: 更新与插入列相关的索引。
    • 执行逻辑:
      • 对表上的每个索引(主键、唯一索引、非唯一索引):
        1. 提取索引列的值。
        2. 将索引项插入到索引结构中(如 B 树)。
  6. RETURNING子句返回阶段
    • 功能: 返回插入的行数据(或表达式结果)。
    • 执行逻辑:
      1. 对每一行插入后的数据,计算RETURNING子句中的表达式。
      2. 将结果返回给客户端(或存储到INTO子句指定的变量中)。
           
           

那么对比一下手写的SQL和SQL实际在数据库层面的读取顺序:
在这里插入图片描述


    最后总结一下,记住这个核心要点🎯:SQL语句的 书写顺序≠执行顺序 。就像做菜要先备料再烹饪一样,数据库总是按照它自己的逻辑流程来处理你的查询。


网站公告

今日签到

点亮在社区的每一天
去签到