【发布时间】:2018-02-13 09:37:08
【问题描述】:
Oracle (12.1) 不使用真正有利的索引。
使用 union all 重写手动查询会有所帮助,但确实很难看。
有什么想法吗?
架构:
- 表“umsatz”,按“monat”的每个值进行分区(= 年 + 月作为数字)。
- (monat, kundengruppe) 上的简单非唯一索引。
- 统计信息可用且是最新的。
- “monat”的选择性不高(每个月都有很多值),但“kundengruppe”列的大部分值都具有选择性。
“kundengruppe”有一个混合直方图,这里使用的具体值不会没有出现在表中(即非常有选择性) - 将它们更改为很少出现的值不会改变任何东西。查询单个频繁出现的值会导致全表/分区扫描 - 在这种情况下是可以的。
查询(减少到相关的最小部分):
SELECT SUM(u.umsatz_euro)
FROM umsatz u
WHERE u.monat BETWEEN 201701 AND 201712
AND u.kundengruppe IN (123,456,987)
以及它的执行计划:
------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 14 | 3074 | 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 14 | | |
| 2 | MAT_VIEW ACCESS BY GLOBAL INDEX ROWID BATCHED | UMSATZ_BUDGETBLATT | 1 | 14 | 3074 | 00:00:01 |
| * 3 | INDEX RANGE SCAN | UMSATZ_BUDGETBLATT_GRP_NUI | 1 | | 3073 | 00:00:01 |
------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 3 - access("UB"."MONAT">=201701 AND "UB"."MONAT"<=201712)
* 3 - filter("UB"."KUNDENGRUPPE"=123 OR "UB"."KUNDENGRUPPE"=456 OR "UB"."KUNDENGRUPPE"=987)
如您所见,只有“monat”通过访问谓词(即作为索引访问)使用,“kundengruppe”通过过滤器使用。
.
如果我将WHERE 更改为简单的AND u.kundengruppe = 123,则索引用于两个值,并且成本会降低很多(3000 -> 4):
------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 14 | 4 | 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 14 | | |
| 2 | MAT_VIEW ACCESS BY GLOBAL INDEX ROWID BATCHED | UMSATZ_BUDGETBLATT | 1 | 14 | 4 | 00:00:01 |
| * 3 | INDEX RANGE SCAN | UMSATZ_BUDGETBLATT_GRP_NUI | 1 | | 3 | 00:00:01 |
------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 3 - access("UB"."MONAT">=201701 AND "UB"."KUNDENGRUPPE"=123 AND "UB"."MONAT"<=201712)
* 3 - filter("UB"."KUNDENGRUPPE"=123)
.
我可以使用语义等效的 UNION ALL手动重写多值-IN:
SELECT SUM(u.umsatz_euro)
FROM
(select * from umsatz u
where u.kundengruppe = 123
union all
select * from umsatz u
where u.kundengruppe = 456
union all
select * from umsatz u
where u.kundengruppe = 987
) u
WHERE u.monat BETWEEN 201701 AND 201712
执行计划使用索引三次,成本很多低于第一个计划 (3000 -> 14),Oracle 甚至将“monat”谓词下推到每个选择中:
--------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
--------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 26 | 12 | 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 26 | | |
| 2 | VIEW | | 3 | 78 | 12 | 00:00:01 |
| 3 | UNION-ALL | | | | | |
| 4 | MAT_VIEW ACCESS BY GLOBAL INDEX ROWID BATCHED | UMSATZ_BUDGETBLATT | 1 | 14 | 4 | 00:00:01 |
| * 5 | INDEX RANGE SCAN | UMSATZ_BUDGETBLATT_GRP_NUI | 1 | | 3 | 00:00:01 |
| 6 | MAT_VIEW ACCESS BY GLOBAL INDEX ROWID BATCHED | UMSATZ_BUDGETBLATT | 1 | 14 | 4 | 00:00:01 |
| * 7 | INDEX RANGE SCAN | UMSATZ_BUDGETBLATT_GRP_NUI | 1 | | 3 | 00:00:01 |
| 8 | MAT_VIEW ACCESS BY GLOBAL INDEX ROWID BATCHED | UMSATZ_BUDGETBLATT | 1 | 14 | 4 | 00:00:01 |
| * 9 | INDEX RANGE SCAN | UMSATZ_BUDGETBLATT_GRP_NUI | 1 | | 3 | 00:00:01 |
--------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 5 - access("UB"."MONAT">=201701 AND "UB"."KUNDENGRUPPE"=123 AND "UB"."MONAT"<=201712)
* 5 - filter("UB"."KUNDENGRUPPE"=123)
* 7 - access("UB"."MONAT">=201701 AND "UB"."KUNDENGRUPPE"=456 AND "UB"."MONAT"<=201712)
* 7 - filter("UB"."KUNDENGRUPPE"=456)
* 9 - access("UB"."MONAT">=201701 AND "UB"."KUNDENGRUPPE"=987 AND "UB"."MONAT"<=201712)
* 9 - filter("UB"."KUNDENGRUPPE"=987)
.
这真的很麻烦,尤其是如果您有一个 Java 列表并通过 Hibernate 轻松地将其绑定到单个绑定变量 (IN (:kgrpList))。
查询提示use_concat 没有帮助。
有什么想法可以改善这种情况吗?
另一个奇怪的事情在这种情况下:
如果我对数字使用绑定变量
SELECT SUM(u.umsatz_euro)
FROM umsatz u
WHERE u.monat BETWEEN 201701 AND 201712
AND u.kundengruppe = to_number(:a) -- also for fixed to_number('a123')
计划使用(慢)索引跳过扫描:
------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 14 | 955 | 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 14 | | |
| 2 | MAT_VIEW ACCESS BY GLOBAL INDEX ROWID BATCHED | UMSATZ_BUDGETBLATT | 1769 | 24766 | 955 | 00:00:01 |
| * 3 | INDEX SKIP SCAN | UMSATZ_BUDGETBLATT_GRP_NUI | 1769 | | 36 | 00:00:01 |
------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 3 - access("UB"."MONAT">=201701 AND "UB"."KUNDENGRUPPE"=TO_NUMBER(:A) AND "UB"."MONAT"<=201712)
* 3 - filter("UB"."KUNDENGRUPPE"=TO_NUMBER(:A))
甲骨文到底为什么要这样做?
【问题讨论】:
-
您最近是否运行过该表的收集统计信息?
-
使用 INDEX 提示的选项怎么样?
-
统计数据是最新的(见编辑文本)。 INDEX 提示不会改变任何东西,可能是因为索引已被使用,但仅用于“monat”列。
-
可以代工吗?
-
INDEX 提示应该强制使用提到的索引而不是自动选择的.. :-/
标签: sql oracle performance optimization