RBa*_*ung 7 performance oracle index-tuning oracle-12c query-performance
我有一个客户端程序正在对一个视图执行查询,该视图外部将一个表连接到另一个表。性能很差,我一直在尝试通过添加正确的索引来调整它。有问题的查询实际上只使用了第二个表,所以我一直在直接针对该表进行测试。
我发现(几个)索引可以很好地用于对表的查询,但是当我将其切换为使用视图时,它们停止使用任何索引,而是对两个表进行全面扫描。由于这些表很大(每个表 2-3 百万行),因此速度非常慢。
为了简单测试,我更改了查询以绕过 并将外连接合并到查询本身中。这成功地重现了问题,但留下了为什么外连接不使用索引的谜团。
这是表格,其中包含我在测试时添加的所有索引:
CREATE TABLE TEST_DATA
(ID NUMBER(11,0) PRIMARY KEY,
FORMATTED_RESULT VARCHAR2(255 BYTE),
F_RESULT NUMBER,
IDNUM NUMBER(11,0),
IDNUM_DESCRIPTION VARCHAR2(128 BYTE),
LAB_NUMBER NUMBER(11,0),
SEQ_NUMBER NUMBER(11,0),
ORDERNO NUMBER(11,0),
SUPPL_FORMATTED_RESULT VARCHAR2(255 BYTE),
SUPPL_IDNUM NUMBER(11,0),
SUPPL_IDNUM_DESCRIPTION VARCHAR2(128 BYTE),
SUPPL_UNIT VARCHAR2(16 BYTE)
) ;
CREATE UNIQUE INDEX TEST_LN_SQN_ORDER ON TEST_DATA (LAB_NUMBER, SEQ_NUMBER, ORDERNO) ;
CREATE INDEX TEST_LN_SQN ON TEST_DATA (LAB_NUMBER, SEQ_NUMBER) ;
CREATE INDEX TD_CUIDD_CUFR ON TEST_DATA (UPPER(COALESCE(SUPPL_IDNUM_DESCRIPTION,IDNUM_DESCRIPTION)), UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))) ;
CREATE INDEX TD_UFR_IDN ON TEST_DATA (UPPER(FORMATTED_RESULT), IDNUM) ;
CREATE INDEX TD_UIDD_UFR ON TEST_DATA (UPPER(IDNUM_DESCRIPTION), UPPER(FORMATTED_RESULT)) ;
CREATE INDEX TD_CUFR_CIDN_SN_LN ON TEST_DATA (UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM), SEQ_NUMBER, LAB_NUMBER) ;
CREATE INDEX TD_SN_LN_CUFR_CIDN ON TEST_DATA (SEQ_NUMBER, LAB_NUMBER, UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM)) ;
CREATE INDEX TD_CUFR_CIDN ON TEST_DATA (UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM)) ;
Run Code Online (Sandbox Code Playgroud)
这是另一个表(我们并不真正用于此查询的表)
CREATE TABLE REQUEST_INFO
(NUMBER(11,0) PRIMARY KEY,
CHARGE_CODE VARCHAR2(32 BYTE),
LAB_NUMBER NUMBER(11,0),
SEQ_NUMBER NUMBER(11,0)
) ;
CREATE INDEX RI_LN_SN ON REQUEST_INFO (LAB_NUMBER, SEQ_NUMBER) ;
CREATE INDEX RI_SN_LN ON REQUEST_INFO (SEQ_NUMBER, LAB_NUMBER) ;
Run Code Online (Sandbox Code Playgroud)
首先,这是直接针对单个表的查询,它成功地使用了其中一个索引。
-- GOOD, Uses index : TD_CUFR_CIDN_SN_LN
select td.LAB_NUMBER
from test_DATA td
where UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549
;
Run Code Online (Sandbox Code Playgroud)
现在这是使用带有内部联接的两个表的查询。这也使用索引并且运行速度很快。
-- GOOD, Uses indexes : TD_CUFR_CIDN_SN_LN AND RI_SN_LN
select TD.LAB_NUMBER
from REQUEST_INFO RI
JOIN TEST_DATA TD ON TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549
Run Code Online (Sandbox Code Playgroud)
这是与左外连接相同的查询,因为它是在视图中编写的。这不使用任何索引并且运行非常缓慢。
-- BAD, does not use indexes
select TD.LAB_NUMBER
from REQUEST_INFO RI
LEFT JOIN TEST_DATA TD ON TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549
;
Run Code Online (Sandbox Code Playgroud)
现在在任何人说它之前:这个查询实际上与前一个在逻辑上相同。这是因为 WHERE 子句对来自外部表 (TD) 的列进行过滤,这有效/逻辑地将外部连接转换为内部连接(这就是为什么条件是否出现在 ON 子句与 WHERE 子句中的原因)。
现在,更奇怪的是,我决定看看如果我让 Outer to Inner 强制更加明确会发生什么:
-- GOOD, Uses indexes : TD_CUFR_CIDN_SN_LN AND RI_SN_LN
select TD.LAB_NUMBER
from REQUEST_INFO RI
LEFT JOIN TEST_DATA TD ON TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549
and TD.LAB_NUMBER IS NOT NULL
;
Run Code Online (Sandbox Code Playgroud)
令人难以置信的是,这奏效了!
所以这里的问题是,1) 为什么 Oracle 不自己解决这个问题?
2) 是否有一些我可以创建的设置或索引等可以让 Oracle 正确解决这个问题并使用索引?
其他注意事项:
该视图被各种其他查询和客户端使用,因此我不能仅将其更改为该查询的内部联接。
客户端正在生成查询,因此很难/几乎不可能使用古怪的特殊情况来更改查询,例如:“将此视图用于此数据,除非您只需要该表中的这些列,然后使用不同的查看“或”当您需要这些列并且仅需要来自该表的这些列时,然后在 WHERE 子句中添加“IS NOT NULL ”
欢迎任何建议或见解。
更新: 我也刚刚在 Oracle 11g 上尝试过,我在那里得到了完全相同的结果。
每个请求,这里是解释计划输出,首先是好的版本,它使用索引:
Rows Plan COST Predicates
3 SELECT STATEMENT 8
3 HASH JOIN 8 Access:TD.LAB_NUMBER=RI.LAB_NUMBER AND TD.SEQ_NUMBER=RI.SEQ_NUMBER
3 NESTED LOOPS 8
STATISTICS COLLECTOR
3 INDEX RANGE SCAN TD_CUFR_CIDN_SN_LN 4 Access:UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))='491(10)376' AND COALESCE(SUPPL_IDNUM,IDNUM)=40549, Filter:TD.LAB_NUMBER IS NOT NULL
1 INDEX RANGE SCAN RI_SN_LN 2 Access:TD.SEQ_NUMBER=RI.SEQ_NUMBER AND TD.LAB_NUMBER=RI.LAB_NUMBER
1 INDEX FAST FULL SCAN RI_SN_LN 2
Run Code Online (Sandbox Code Playgroud)
现在是糟糕的版本:
Rows Plan COST Predicates
31939030 SELECT STATEMENT 910972
FILTER Filter:UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))='491(10)376' AND COALESCE(SUPPL_IDNUM,IDNUM)=40549
31939030 HASH JOIN OUTER 910972 Access:TD.LAB_NUMBER(+)=RI.LAB_NUMBER AND TD.SEQ_NUMBER(+)=RI.SEQ_NUMBER
6213479 TABLE ACCESS FULL REQUEST_INFO 58276
56276228 TABLE ACCESS FULL TEST_DATA 409612
Run Code Online (Sandbox Code Playgroud)
这主要是对第 1 部分的部分回答,并带有一些猜测。你和我都知道以下查询:
select TD.LAB_NUMBER
from REQUEST_INFO RI
LEFT JOIN TEST_DATA TD ON TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549;
Run Code Online (Sandbox Code Playgroud)
相当于这个查询:
select TD.LAB_NUMBER
from REQUEST_INFO RI
INNER JOIN TEST_DATA TD ON
TD.LAB_NUMBER = RI.LAB_NUMBER
AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
AND UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549;
Run Code Online (Sandbox Code Playgroud)
但是,这并不意味着 Oracle 知道这两个查询是等效的。Oracle 需要两个查询相等才能使用索引TD_CUFR_CIDN_SN_LN。我们在这里希望的是一个OUTER JOINtoINNER JOIN转换。我没有很幸运地找到这方面的好信息,所以让我们看看解释计划:
添加TD.LAB_NUMBER IS NOT NULL到WHERE子句是一种非常直接的方式,让 Oracle 知道可以OUTER JOIN进行转换。INNER JOIN通过查看突出显示的行,我们可以看到它发生了。我认为几乎任何列都允许转换,尽管选择错误的列可能会更改查询结果。
如果我们尝试稍微复杂一点的过滤器,例如(TD.LAB_NUMBER IS NOT NULL OR TD.SEQ_NUMBER IS NOT NULL)连接转换不会发生:
我们可以推断出这OUTER JOIN确实是一个INNER JOIN,但查询优化器可能没有被编程来做到这一点。在原始查询中,您的COALESCE()表达式可能过于复杂,查询优化器无法应用查询转换。
这是一些示例的数据库小提琴。
对于第二个问题,我无法想出解决这个问题的方法。您可以尝试利用表消除。正如您所说,这个查询甚至不需要表REQUEST_INFO。但是,有一些限制:
目前表消除有一些限制:
不支持多列主键-外键约束。
在查询中的其他地方引用连接键将阻止表消除。对于内部联接,联接两侧的联接键是等效的,但如果查询包含对表中本来可以消除的联接键的其他引用,则会阻止消除。解决方法是重写查询以引用另一个表中的联接键(我们意识到这并不总是可行)。
也许有一种方法可以用它来解决这个问题,但我无法解决这些限制。