Oracle 优化器尴尬地不喜欢使用索引
Oracle Optimizer Awkwardly Doesn't Prefer to Use Index
我正在加入 table 本身,但虽然我希望此操作使用索引,但似乎没有。 table(MY_TABLE
) 上有 100 万条记录,查询 I 运行 正在对大约 10,000 条记录执行。(所以它低于整个 [=37= 的 %1 ].)
测试用例:
explain plan for
SELECT *
FROM SCHM.MY_TABLE A1, SCHM.MY_TABLE A2
WHERE (A1.K_ID = '123abc')
AND A1.HDT = A2.HDT
AND A2.C_DATE BETWEEN A1.SYSDATE - 0.0004
AND A1.SYSDATE + 0.0004
AND A1.GKID = A2.GKID;
Plan hash value: 1210306805
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 81 | 28 (0)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 3 | 81 | 28 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE | 3 | 45 | 7 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_MY_TABLE_C_DATE | 3 | | 4 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE | 17 | 204 | 21 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | IX_MY_TABLE_K_ID | 17 | | 4 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(SYSDATE@!+0.00004>=SYSDATE@!-0.00004)
2 - access("A1"."HDT"="A2"."HDT" AND
"A1"."GKID"="A2"."GKID")
4 - access("A2"."C_DATE">=SYSDATE@!-0.00004 AND
"A2"."C_DATE"<=SYSDATE@!+0.00004)
6 - access("A1"."K_ID"=U'123abc')
在上面的语句中,可以看出使用了C_DATE上的索引。
然而,在下面的语句中,没有使用 C_DATE 上的索引。所以,查询 运行 真的很慢。
真实案例:
explain plan for
SELECT *
FROM SCHM.MY_TABLE A1, SCHM.MY_TABLE A2
WHERE (A1.K_ID = '123abc')
AND A1.HDT = A2.HDT
AND A2.C_DATE BETWEEN A1.C_DATE - 0.0004
AND A1.C_DATE + 0.0004
AND A1.GKID = A2.GKID;
Plan hash value: 1063167343
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4187K| 998M| 6549K (1)| 00:04:16 |
|* 1 | HASH JOIN | | 4187K| 998M| 6549K (1)| 00:04:16 |
| 2 | JOIN FILTER CREATE | :BF0000 | 17 | 2125 | 21 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE | 17 | 2125 | 21 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_MY_TABLE_K_ID | 17 | | 4 (0)| 00:00:01 |
| 5 | JOIN FILTER USE | :BF0000 | 1429M| 166G| 6546K (1)| 00:04:16 |
|* 6 | TABLE ACCESS STORAGE FULL | MY_TABLE | 1429M| 166G| 6546K (1)| 00:04:16 |
----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access(A1.HDT=A2.HDT AND
A1.GKID=A2.GKID)
filter(A2.C_DATE>=INTERNAL_FUNCTION(A1.C_DATE)-0.00004 AND
A2.C_DATE<=INTERNAL_FUNCTION(A1.C_DATE)+0.00004)
4 - access(A1.K_ID=U'123abc')
6 - storage(SYS_OP_BLOOM_FILTER(:BF0000,A2.HDT,A2.HDT))
filter(SYS_OP_BLOOM_FILTER(:BF0000,A2.HDT,A2.HDT))
如果我使用提示 /*+index(A2,IX_MY_TABLE_C_DATE )*/
,一切都很好,使用了索引并且查询 运行s 快如我所愿。
真实案例中的查询是应用创建的,无法更改。
Index Information:
K_ID, not unique, position 1
HDT, not unique, position 1
C_DATE, not unique, position 1
ID Unique and Primary Key, position 1
为了在实际情况下使用索引,我必须更改什么?
您的自加入有 3 个加入条件(HDT、GKID、C_DATE)和 1 个非加入条件(K_ID)。所以对我来说这似乎很自然,如果 DBMS 从匹配 K_ID 的记录开始,然后查找所有匹配的其他记录。
对于这种情况,我建议使用以下索引:
create index idx1 on my_table(k_id, hdt, gkid, c_date);
create index idx2 on my_table(hdt, gkid, c_date);
如果每个 k_id 的记录很少,我相信 Oracle 会使用索引。如果有很多,Oracle 可能仍然使用第二个进行散列连接。
嗯,第二个查询比较慢,因为它与第一个查询有很大不同。它在表之间有一个额外的连接:
AND A2.C_DATE BETWEEN A1.C_DATE - 0.0004
AND A1.C_DATE + 0.0004
在一百万行中,这会造成损失。
第一个查询没有这个连接条件,两个表都是:
- 先过滤。这使用索引很快:只有3行和17行。
- 第二次加入他们。连接3行和17行不需要任何时间。
第二个查询需要执行:
- 首先是一个巨大的(散列)连接,returns 可能超过 100K 行。
- 稍后过滤。
这样慢多了。
我建议添加以下索引,然后重试:
create index ix_1 (k_id);
create index ix_2 (hdt, gkid, c_date);
只是补充一下,每当你遇到这样的情况时
- 速度慢 SQL,
- 已经确定了一个可以实现的提示
更快
- 无法更改 SQL 因为源不可用
那么这是 SQL 计划基线的完美案例。这些使您可以针对现有 SQL 锁定 "good" 计划,而无需触及 SQL 语句本身。
描述 SPM 的整个系列都在下面的链接中,但是指向 "part 4" 的链接详细介绍了您想要实现的目标的具体示例。
https://blogs.oracle.com/optimizer/sql-plan-management-part-1-of-4-creating-sql-plan-baselines
https://blogs.oracle.com/optimizer/sql-plan-management-part-2-of-4-spm-aware-optimizer
https://blogs.oracle.com/optimizer/sql-plan-management-part-3-of-4:-evolving-sql-plan-baselines
https://blogs.oracle.com/optimizer/sql-plan-management-part-4-of-4:-user-interfaces-and-other-features
我正在加入 table 本身,但虽然我希望此操作使用索引,但似乎没有。 table(MY_TABLE
) 上有 100 万条记录,查询 I 运行 正在对大约 10,000 条记录执行。(所以它低于整个 [=37= 的 %1 ].)
测试用例:
explain plan for
SELECT *
FROM SCHM.MY_TABLE A1, SCHM.MY_TABLE A2
WHERE (A1.K_ID = '123abc')
AND A1.HDT = A2.HDT
AND A2.C_DATE BETWEEN A1.SYSDATE - 0.0004
AND A1.SYSDATE + 0.0004
AND A1.GKID = A2.GKID;
Plan hash value: 1210306805
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 81 | 28 (0)| 00:00:01 |
|* 1 | FILTER | | | | | |
|* 2 | HASH JOIN | | 3 | 81 | 28 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE | 3 | 45 | 7 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_MY_TABLE_C_DATE | 3 | | 4 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE | 17 | 204 | 21 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | IX_MY_TABLE_K_ID | 17 | | 4 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(SYSDATE@!+0.00004>=SYSDATE@!-0.00004)
2 - access("A1"."HDT"="A2"."HDT" AND
"A1"."GKID"="A2"."GKID")
4 - access("A2"."C_DATE">=SYSDATE@!-0.00004 AND
"A2"."C_DATE"<=SYSDATE@!+0.00004)
6 - access("A1"."K_ID"=U'123abc')
在上面的语句中,可以看出使用了C_DATE上的索引。
然而,在下面的语句中,没有使用 C_DATE 上的索引。所以,查询 运行 真的很慢。
真实案例:
explain plan for
SELECT *
FROM SCHM.MY_TABLE A1, SCHM.MY_TABLE A2
WHERE (A1.K_ID = '123abc')
AND A1.HDT = A2.HDT
AND A2.C_DATE BETWEEN A1.C_DATE - 0.0004
AND A1.C_DATE + 0.0004
AND A1.GKID = A2.GKID;
Plan hash value: 1063167343
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4187K| 998M| 6549K (1)| 00:04:16 |
|* 1 | HASH JOIN | | 4187K| 998M| 6549K (1)| 00:04:16 |
| 2 | JOIN FILTER CREATE | :BF0000 | 17 | 2125 | 21 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| MY_TABLE | 17 | 2125 | 21 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IX_MY_TABLE_K_ID | 17 | | 4 (0)| 00:00:01 |
| 5 | JOIN FILTER USE | :BF0000 | 1429M| 166G| 6546K (1)| 00:04:16 |
|* 6 | TABLE ACCESS STORAGE FULL | MY_TABLE | 1429M| 166G| 6546K (1)| 00:04:16 |
----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access(A1.HDT=A2.HDT AND
A1.GKID=A2.GKID)
filter(A2.C_DATE>=INTERNAL_FUNCTION(A1.C_DATE)-0.00004 AND
A2.C_DATE<=INTERNAL_FUNCTION(A1.C_DATE)+0.00004)
4 - access(A1.K_ID=U'123abc')
6 - storage(SYS_OP_BLOOM_FILTER(:BF0000,A2.HDT,A2.HDT))
filter(SYS_OP_BLOOM_FILTER(:BF0000,A2.HDT,A2.HDT))
如果我使用提示 /*+index(A2,IX_MY_TABLE_C_DATE )*/
,一切都很好,使用了索引并且查询 运行s 快如我所愿。
真实案例中的查询是应用创建的,无法更改。
Index Information:
K_ID, not unique, position 1
HDT, not unique, position 1
C_DATE, not unique, position 1
ID Unique and Primary Key, position 1
为了在实际情况下使用索引,我必须更改什么?
您的自加入有 3 个加入条件(HDT、GKID、C_DATE)和 1 个非加入条件(K_ID)。所以对我来说这似乎很自然,如果 DBMS 从匹配 K_ID 的记录开始,然后查找所有匹配的其他记录。
对于这种情况,我建议使用以下索引:
create index idx1 on my_table(k_id, hdt, gkid, c_date);
create index idx2 on my_table(hdt, gkid, c_date);
如果每个 k_id 的记录很少,我相信 Oracle 会使用索引。如果有很多,Oracle 可能仍然使用第二个进行散列连接。
嗯,第二个查询比较慢,因为它与第一个查询有很大不同。它在表之间有一个额外的连接:
AND A2.C_DATE BETWEEN A1.C_DATE - 0.0004
AND A1.C_DATE + 0.0004
在一百万行中,这会造成损失。
第一个查询没有这个连接条件,两个表都是:
- 先过滤。这使用索引很快:只有3行和17行。
- 第二次加入他们。连接3行和17行不需要任何时间。
第二个查询需要执行:
- 首先是一个巨大的(散列)连接,returns 可能超过 100K 行。
- 稍后过滤。
这样慢多了。
我建议添加以下索引,然后重试:
create index ix_1 (k_id);
create index ix_2 (hdt, gkid, c_date);
只是补充一下,每当你遇到这样的情况时
- 速度慢 SQL,
- 已经确定了一个可以实现的提示 更快
- 无法更改 SQL 因为源不可用
那么这是 SQL 计划基线的完美案例。这些使您可以针对现有 SQL 锁定 "good" 计划,而无需触及 SQL 语句本身。
描述 SPM 的整个系列都在下面的链接中,但是指向 "part 4" 的链接详细介绍了您想要实现的目标的具体示例。
https://blogs.oracle.com/optimizer/sql-plan-management-part-1-of-4-creating-sql-plan-baselines https://blogs.oracle.com/optimizer/sql-plan-management-part-2-of-4-spm-aware-optimizer https://blogs.oracle.com/optimizer/sql-plan-management-part-3-of-4:-evolving-sql-plan-baselines https://blogs.oracle.com/optimizer/sql-plan-management-part-4-of-4:-user-interfaces-and-other-features