原文链接:[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 种可能的搜索策略来查找记录,选择哪种路径由优化器决定:
- 搜索整个表格
- 使用主索引进行搜索
- 使用搜索的二级索引
优化器:优化器是对 SQL 语句进行解析,并考虑每个访问路径(全量扫描、主索引、二级索引)并制定 SQL 语句执行计划的数据库程序。有 2 种类型的优化器:
- RBO(Rule-Based Optimization) 基于规则的优化器:根据可用的索引和
WHERE
子句中使用的字段,RBO 创建其执行计划。WHERE
子句中的大多数导入条件是使用EQUALS
条件指定并出现在索引中的字段。 - CBO(Cost-Based Optimization) 基于成本的优化器:为了创建 SQL 语句的执行计划,CBO 考虑与 RBO 相同的标准
-
表大小:对于小表,CBO 决定避免索引,转而使用更有效的全表扫描。大型表更有可能通过索引进行访问。
-
索引字段的选择性:给定索引字段的选择性是当 SQL 语句在该字段中搜索特定非重复值时读取的表部分的平均大小。例如:
- 如果
VBAK
表中有 200 个订单类型和 10,000 个实际订单,则字段文档类型的选择性为 10,000 除以 200,即 10,000 的 50 或 2%。字段中非重复值的数量越大,优化程序使用基于该字段的索引的可能性就越大。
- 如果
-
物理存储:优化器考虑必须在硬盘驱动器上物理读取多少索引数据块或页面。需要读取的物理内存越多,使用索引的可能性就越小。
示例 1: 创建数据库表 ZKUM_TEST
,包含字段从 F1、F2、...一直到 F10。并且 F1、F2、F3、F4 是表的关键字段,即主键。现在考虑以下 select
查询语句:
SELECT * FROM ZKUM_TEST INTO TABLE ITAB WHERE F2 = '1' and F3 = '3'
:这里优化器不会使用主索引进行搜索操作,因为WHERE
子句中缺少 F1 字段。因此,将执行全表扫描。SELECT * FROM ZKUM_TEST INTO TABLE ITAB WHERE F1 = '2' and F2 = '3'
: 如果字段 F1 和 F2 上没有二级索引,则优化器将选择主索引,因为 4 个字段中的两个字段是匹配的。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 编程的规则:
- SQL 语句必须具有仅传输最少量数据的
WHERE
子句。避免在WHERE
子句中使用NOT
条件。这些不能通过索引进行处理。避免使用相同的选择,您可以在ST05
中识别这一点 - 使用
SELECT column1 column2 ....
,而不是SELCT *
WHERE
条件必须简单;否则,优化程序可能会决定错误的索引或根本不使用索引。如果WHERE
子句使用AND
和EQUALS
条件指定索引的每个字段,则该WHERE
子句处理起来就很简单- 在
LOOP
中使用FOR ALL ENTRIES
而不是SELECT
。 使用FAE
时,请确保内表不为空,并且不得包含重复项 。根据数据库系统的不同,数据库接口将 FAE 转换为各种 SQL 语句,它可以使用IN
或UNION
运算符转换为 SQL 语句
多表查询大量数据,建立数据库视图
如果要从多个表中提取大量数据,则最好创建数据库视图,而不是使用 FOR ALL ENTRIES
。
来看示例 2:
运行以下 select
语句的 SQL trace:
- 直接查询整个表
SQL
SELECT mandt vbeln posnr FROM vbap INTO TABLE itab WHERE erdat in s_erdat.
- 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*。
创建二级索引的规则:
- 在索引中仅包含选择性字段:ex customer number、Material、Contract number
- 在索引中包含少量字段
- 将选择性字段放置在索引的左侧
- 索引应该是不连续性的,避免创建两个或多个具有基本相同字段的索引
- 为每个表创建几个索引
如果通过特定索引字段进行搜索的 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
:重新创建索引