ABAP 中 SQL 语句的性能优化

原文链接:Performance optimization of SQL statements in ABAP(Performance optimization of SQL statements in ABAP)

作者:community.sap.com/t5/user/vie...

SQL 语句语法是否合理,将直接影响程序的执行效率。

在本文中,我解释了数据库优化器的概念,以及它如何解析 SQL 查询以创建评估路径。我举例说明了如何通过更改 WHERE 子句来提高 ABAP 中 SQL 语句的性能。我假设读者熟悉交易 SE30 和 ST05。

注: 以下 SQL 追踪来自 DB2 数据库,其他数据库的结果可能有所不同。

SELECT 语句

先来考虑简单的 SELECT 语句:

SQL 复制代码
SELECT * from MARA where MATNR = 'ACFBAS2' 。

这里数据库有 3 种可能的搜索策略来查找记录,选择哪种路径由优化器决定:

  1. 搜索整个表格
  2. 使用主索引进行搜索
  3. 使用搜索的二级索引

优化器:优化器是对 SQL 语句进行解析,并考虑每个访问路径(全量扫描、主索引、二级索引)并制定 SQL 语句执行计划的数据库程序。有 2 种类型的优化器:

  • RBO(Rule-Based Optimization) 基于规则的优化器:根据可用的索引和 WHERE 子句中使用的字段,RBO 创建其执行计划。WHERE 子句中的大多数导入条件是使用 EQUALS 条件指定并出现在索引中的字段。
  • CBO(Cost-Based Optimization) 基于成本的优化器:为了创建 SQL 语句的执行计划,CBO 考虑与 RBO 相同的标准
  1. 表大小:对于小表,CBO 决定避免索引,转而使用更有效的全表扫描。大型表更有可能通过索引进行访问。

  2. 索引字段的选择性:给定索引字段的选择性是当 SQL 语句在该字段中搜索特定非重复值时读取的表部分的平均大小。例如:

    • 如果 VBAK 表中有 200 个订单类型和 10,000 个实际订单,则字段文档类型的选择性为 10,000 除以 200,即 10,000 的 50 或 2%。字段中非重复值的数量越大,优化程序使用基于该字段的索引的可能性就越大。
  3. 物理存储:优化器考虑必须在硬盘驱动器上物理读取多少索引数据块或页面。需要读取的物理内存越多,使用索引的可能性就越小。

示例 1: 创建数据库表 ZKUM_TEST ,包含字段从 F1、F2、...一直到 F10。并且 F1、F2、F3、F4 是表的关键字段,即主键。现在考虑以下 select 查询语句:

  1. SELECT * FROM ZKUM_TEST INTO TABLE ITAB WHERE F2 = '1' and F3 = '3':这里优化器不会使用主索引进行搜索操作,因为 WHERE 子句中缺少 F1 字段。因此,将执行全表扫描。
  2. SELECT * FROM ZKUM_TEST INTO TABLE ITAB WHERE F1 = '2' and F2 = '3' : 如果字段 F1 和 F2 上没有二级索引,则优化器将选择主索引,因为 4 个字段中的两个字段是匹配的。
  3. SELECT * FROM ZKUM_TEST INTO TABLE ITAB WHERE F1 <> '2' and F2 = '3' and F3 = '3':此处优化器不会使用主索引,因为运算符 NOT 用于比较列 F1。如果列 F2、F3 上有任何二级索引,则优化器将使用二级索引。

为了对最佳访问做出正确的决策,CBO 需要有关表和索引大小的统计信息。必须定期生成统计信息以使其保持最新状态。标准 SAP 报告可用于生成这些统计信息(事务 DB13)。CBO 的优点是它的灵活性,因为它考虑了指定字段的选择性。

例如:

SQL 复制代码
SELECT * FROM MARA WHERE MANDT= '020' and BISMT = 'a1'. 

在这里,CBO 知道 MANDT 只包含一个不同的值将决定全表扫描,而 RBO 将选择效率较低的索引范围扫描。

标题 处理方法 索引字段 运行时间/微秒
无表格访问统计数据/无基于字段 BISMT 的二级索引 索引范围扫描 MANDT,MATNR 3,500,000
有表格访问统计数据/无基于字段 BISMT 的二级索引 全表扫描 N/A 500,000
使用表格访问统计/基于字段 BISMT 的二级索引 索引范围扫描 BISMT 3000

高效 SQL 编程的规则:

  1. SQL 语句必须具有仅传输最少量数据的 WHERE 子句。避免在 WHERE 子句中使用 NOT 条件。这些不能通过索引进行处理。避免使用相同的选择,您可以在 ST05 中识别这一点
  2. 使用 SELECT column1 column2 ....,而不是 SELCT *
  3. WHERE 条件必须简单;否则,优化程序可能会决定错误的索引或根本不使用索引。如果 WHERE 子句使用 AND 和 EQUALS 条件指定索引的每个字段,则该 WHERE 子句处理起来就很简单
  4. 在 LOOP 中使用 FOR ALL ENTRIES 而不是 SELECT。 使用 FAE 时,请确保内表不为空,并且不得包含重复项 。根据数据库系统的不同,数据库接口将 FAE 转换为各种 SQL 语句,它可以使用 IN 或 UNION 运算符转换为 SQL 语句

多表查询大量数据,建立数据库视图

如果要从多个表中提取大量数据,则最好创建数据库视图,而不是使用 FOR ALL ENTRIES。

来看示例 2:

运行以下 select 语句的 SQL trace:

  1. 直接查询整个表
SQL 复制代码
SELECT mandt vbeln posnr FROM vbap INTO TABLE itab WHERE erdat in s_erdat.
  1. FOR ALL ENTRIES IN
ABAP 复制代码
IF itab[] IS NOT INITIAL.
  select mandt vbeln posnr from vbap into table itab
      FOR ALL ENTRIES IN itab
    where vbeln = itab-vbeln and posnr = itab-posnr.
ENDIF.

第一种 SQL

对于第一个 SELECT,将扫描整个表,因为找不到匹配的索引(因为 WHERE 子句中提到的列既不是主键的一部分,也不是第二个键的一部分)。要了解数据库的命中数,请单击菜单路径 "Trace List" 中的 "Summarize Trace by SQL statements" ,这将对相同的选择进行分组。

对于第一次选择仅一次命中到 DB,而对于第二次选择 3210 次命中到 DB,有关详细信息,请参见下文。"Executions" 列为您提供了对数据库的命中数。

单击"解释"按钮,首先选择以了解访问路径,即使用哪个索引。这里没有找到匹配的索引,所以整个表将被扫描。

在下面的屏幕截图中,您只能看到一列,即 MANDT 与原始索引的 3 列匹配。所以整个表都被扫描了。

第二种 SQL

第二选择: 内部表 ITAB[] 有大约 32K 条记录。

在 SQL 跟踪中,单击第二个 SQL 的显示,您会注意到 FOR ALL ENTRIES IN TABLE 构造已更改为 " UNION ALL",并且 DB 优化器会进行子查询。

DB 的点击数是 3210

UNION : UNION 命令用于从两个表中选择相关信息,与 JOIN 命令非常相似。 UNION ALL 命令等于 UNION 命令,只不过 UNION ALL 选择所有值。

在上面的示例中,单个 SELECT 语句被优化的数据库拆分为多个选择,并带有子查询(即 UNION ALL)并传递到数据库。

在此示例中,ITAB[] 大约有 32K 条记录,每次选择都会传递 10 条记录,从而向数据库发送 3210 次命中。

第一个 SELECT 的总持续时间:2,167,664 微秒

第二个 SELECT 的总持续时间:6,739,983 微秒。

现在更改第二种 SQL 方式,这样优化器就不会使用带有 UNION ALL 构造的子查询。POSNR 被注释掉。

修改后的代码:

sql 复制代码
SORT ITAB BY VBELN.
DELETE ADJACENT DUPLICATES FROM ITAB COMPARING VBELN.  

按 VBELN 对 ITAB[] 进行排序。然后从 ITAB 中删除相邻的重复项,比较 VBELN。

SQL 复制代码
SELECT mandt vbeln posnr FROM vbap INTO TABLE itab
      for ALL ENTRIES IN itab
    where vbeln = itab-vbeln ." and posnr = itab-posnr.

DB 的命中次数下降到 189 次,总持续时间下降到 453,742 微秒。

单击"解释"按钮以了解访问路径。在本例中,数据库优化器将 FOR ALL ENTRIES IN TABLE 构造更改为 'IN' 子句。

通过减少对数据库的命中数,在每个选择中传递一堆值(大约 30 个合约编号)。优化器仍然使用主键索引作为 WHERE 子句中提到的两列 (MANDT,VEBLN) 与表的主键匹配。

跟踪中显示的运行时不仅包括数据库提供请求的数据所需的时间,还包括在数据库和应用程序服务器之间传输数据所需的时间。如果网络通信中出现性能问题,则 SQL 语句的运行时间会增加。因此,在得出任何结论之前,请进行 2-3 次跟踪。

如上所述,如果 SELECT 语句存在任何性能问题,首先需要检查的是 WHERE caluse,修改 WHERE 子句并查看优化器是否使用任何可用索引。如果没有使用可用索引,则最好创建二级索引以提高性能。

请注意:当您从多个表中提取数据时,最好使用 INNER JOIN 联接这些表,因为 INNER JOIN 的性能优于 FOR ALL ENTRIES。

示例3:

考虑以下选择:这里我正在拉取合同行项 VC 特征详细信息。VBAP-CUOBJ 是行项 VC 配置编号

SQL 复制代码
SELECT a~instance  b~in_recno b~symbol_id c~atinn  c~atwrt  c~atflv
    FROM ( ( ibin AS a INNER JOIN ibinvalues AS b ON a~in_recno = b~in_recno )
    INNER JOIN ibsymbol AS c  ON b~symbol_id = c~symbol_id )
    INTO TABLE gt_char_val
    FOR ALL ENTRIES IN l_cabn
    WHERE a~instance = it_vbap-cuobj AND c~atinn = l_cabn-atinn .

与其在 IBIN、IBINVALUES 和 IBSYMBOL 上编写 3 个单独的 select 语句,不如使用 INNER JOIN。

SQL 跟踪显示每个表都使用可用索引,即表 IBIN 索引"C",表 IBINVALUES 索引"SW0"使用,表 IBSYMBOL 索引"SEL"使用,因此查询性能良好。

因此,当您在多个表上使用 INNER JOIN 时,最好进行 SQL 跟踪并检查访问路径以查看是否使用了可用的索引。

索引:IBIN~C is on columns MANDT and INSTANCE

索引:SWO on table IBINVALUES is on columns MANDT, IN_RECNO, SYMBOL_ID, ATZIS and ASSTYP

索引: SEL on table IBSYMBOL is on columns MANDT, ATINN, ATWRT, ATFLV and SYMBOL_ID

创建二级索引

创建或更改二级索引可能会提高或降低 SQL 语句的性能,在创建二级索引之前,请检查是否可以以可以使用可用索引的方式重写 ABAP 程序。

切勿在没有 SAP 建议的情况下在 SAP 基础表上创建二级索引,例如 D010、D020、DD*。

创建二级索引的规则:

  1. 在索引中仅包含选择性字段:ex customer number、Material、Contract number
  2. 在索引中包含少量字段
  3. 将选择性字段放置在索引的左侧
  4. 索引应该是不连续性的,避免创建两个或多个具有基本相同字段的索引
  5. 为每个表创建几个索引

如果通过特定索引字段进行搜索的 SQL 语句将导致读取整个索引的 5% 到 10% 以上,则基于成本的优化程序不会认为该索引有用,而是选择全表扫描作为最有效的访问方法。

示例 1 :考虑下面的 select 语句

sql 复制代码
SELECT MANDT VBELN posnr FROM VBAp into TABLE ITAB  WHERE VGBEL IN s_VGBEL.

VGBEL(参考文档编号)不是主键的一部分,也不存在二级主键。在 DB2 中选择上述 SQL 跟踪。

SQL 跟踪:

如果查看访问路径,则未找到匹配的索引。因此,我们需要检查在 MANDT 和 VGBEL 上创建二级索引是否是一个好的候选项。

现在转到事务 DB05 并输入表名并指定要在其上创建索引的文件,然后单击执行:

如果表大小较大,请选中"在后台提交分析"复选框。

输出:

输出显示 VBAP 有 317,272 行,这 317K 条记录中的不同 VGBEL 值的数目为 5644。

在这 5644 个不同的值中

  • 4430 参考文档在 VBAP 表中的条目少于 10 个:表示如果您从 VBAP 中使用 count(*) 并输入这些 4430 参考文档编号中的任何一个,则行项目计数将在 1 到 10 之间。
  • 1099 参考文档的条目介于 10 到 100 之间
  • 101 参考文档的条目介于 101 到 1000 之间
  • 13 份参考文件的条目在 1001 到 10,000 之间
  • 只有 1 个参考文档在 VBAP 中的记录超过 100,000:表示如果您从 VBAP 执行 count(*) 并输入此 ref. doc 数字,您将获得行项目计数 >= 100,001

DB05 分析表明,大多数参考文档的记录计数在 1 到 1000 之间,这意味着这是创建辅助 INDEX 的良好候选项。

现在,在 SE11 事务中的 MANDT 和 VGBEL 列的 VBAP 上创建一个辅助索引,然后再次运行相同的查询。

SQL 跟踪显示运行时间从 2,165,827 毫秒下降到 2,026 毫秒,差异巨大。

示例 2 :如果要在表的多个列上创建索引,那么根据 DB05 中的这些索引字段分析该表。考虑以下对 TCURR 表的分析。

我在 TCURR 表上运行 DB05 的字段 MANDT、KURST、FCURR、TCURR 和 GDATU

解释:

  • KURST 行:TCURR 表中不同汇率类型的编号为 22 种,其中 2 种的记录计数在 1 到 10 之间。 三种速率类型的记录计数介于 11 到 100 之间。14 种汇率类型计数在 101 到 100 之间不等
  • FCURR 行:KURST 和 FCURR 组合的不同值数为 1014,其中 785 个组合的记录计数在 1 到 10 之间。KURST 和 FCURR 的 137 个组合的记录数量在 11 到 100 之间不等
  • TCURR 行:KURST、FCURR 和 TCURR 组合的非重复值数为 1019,其中 790 个组合的记录计数在 1 到 10 之间不等
  • GDATU 行:KURST、FCURR & TCURR 和 GDATU 组合的不同值数为 35K,即每个组合只有一条记录,因为它们是主要索引的一部分。

这样,DB05 分析将帮助您确定为创建索引而选择的列是否适合。如果您在 DB05 中看到更多记录的记录计数> 10,000,那么它就不是创建索引的良好候选者。

考虑下面对 T-code 列上的 BKPF 表的分析,这绝对不是一个好的候选项:

一些有用的 T-code 代码:

  • DB20
  • ST04, ST05
  • SE30
  • DB05
  • DB02:对于缺失的索引
  • SE14:重新创建索引
相关推荐
小码哥_常4 小时前
一套自己部署的 IM,客户端是同一份 Flutter
前端·后端
学长毕设7 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
努力的小雨9 小时前
给 AI 眼镜开发应用有多难?我从零做了一个陪练,还上架了
后端
excel11 小时前
双 Token 与前端使用 Crypto 公私钥鉴权换 Token,哪种方案更好?
前端·后端
AI入职记11 小时前
AI 代码审查召回率实测:20 个骗过单元测试的变异体,抓到 13 个
后端
浮链序11 小时前
别让 AI 客服把用户当猴耍:百来行 Python 把「转人工」做成一条高可用链路
后端
属于自己的天空11 小时前
Claude Code 插件完全指南:官方市场怎么用,怎么自己写(v2.1.269+)
后端
Tokenfree12 小时前
给大模型调用加一个熔断器:60 行代码,三个阈值
后端
Jason_chen12 小时前
Linux 进程调度器深入解析与实时性优化指南(基于 Linux 5.10+)
后端
1360967572312 小时前
三个 Agent 干同一个活,职责边界到底该怎么划
后端