请注意,为了便于阅读,我已经修改了表/字段名称等。一些原始名称相当混乱。
我有三个不同的表:
Retailer (Id+Code is a unique key)
- Id
- Code
- LastReturnDate
- ...
Delivery/DeliveryHistory (combination of Date+RetailerId is unique)
- Date
- RetailerId
- HasReturns
- ...
Delivery
并且DeliveryHistory
几乎相同。数据会定期移动到历史记录表中,并且没有万无一失的方法可以知道最后一次发生的时间。一般来说,Delivery-table 非常小——通常少于 100,000 行——而历史表通常会有数百万行。
我的任务是根据 Delivery或DeliveryHistory 中为 trueLastReturnDate
的当前最高日期值更新每个零售商的字段。HasReturns
以前,这已通过定义如下的视图解决:
SELECT Id, Code, MAX(Date) Date
FROM Delivery
WHERE HasReturns = 1
GROUP BY Id, Code
UNION
SELECT Id, Code, MAX(Date) Date
FROM DeliveryHistory
WHERE HasReturns = 1
GROUP BY Id, Code
以及以下 UPDATE 语句:
UPDATE Retailer SET LastReturnDate = (
SELECT MAX(Date) FROM DeliveryView
WHERE Retailer.Id = DeliveryView.Id AND Retailer.Code = DeliveryView.Code)
WHERE Code = :Code AND EXISTS (
SELECT * FROM DeliveryView
WHERE Retailer.Id = DeliveryView.Id AND Retailer.Code = DeliveryView.Code
HAVING
MAX(Date) > LastReturnDate OR
(LastReturnDate IS NULL AND MAX(Date) IS NOT NULL))
EXISTS 子句防止更新当前值大于新值的字段,但这实际上不是一个重要的问题,因为很难看出在正常程序执行期间如何发生这种情况。还要注意这AND Max(Date) IS NOT NULL
部分实际上是多余的,因为在 DeliveryView 中 Date 不可能为空。但 EXISTS 子句似乎实际上略微提高了性能。
但是,UPDATE 的性能最近非常糟糕。在 Retailer 表仅包含 1000-2000 个相关条目的数据库中,UPDATE 已花费超过五分钟的时间运行。请注意,即使我删除了整个 EXISTS 子句,它也会这样做,即使用这个非常简单的语句:
UPDATE Retailer SET LastReturnDate = (
SELECT MAX(Date) FROM DeliveryView
WHERE Retailer.Id = DeliveryView.Id AND Retailer.Code = DeliveryView.Code)
WHERE Code = :Code
因此,我一直在寻找更好的解决方案。我的第一个想法是创建一个临时表,但过了一会儿我尝试将其写为 MERGE 语句:
MERGE INTO Retailer
USING (SELECT Id, Code, MAX(Date) Date FROM DeliveryView GROUP BY Id, Code)
ON (Retailer.Id = DeliveryView.Id AND Retailer.Code = DeliveryView.Code)
WHEN MATCHED THEN
UPDATE SET LastReturnDate = Date WHERE Code = :Code
这似乎有效,而且比 UPDATE 快一个数量级以上。
我有三个问题:
- 我能否确定这在所有情况下都与 UPDATE 具有相同的效果(忽略 LastReturnDate 已经大于 MAX(Date) 的边缘情况)?
- 为什么速度这么快?
- 有更好的解决方案吗?
查询计划
合并计划
成本:25,831,字节:1,143,828
平白的语言
- 读取表 SCHEMA.Delivery 中的每一行。
- 行被排序以便被分组。
- 读取表 SCHEMA.DeliveryHistory 中的每一行。
- 行被排序以便被分组。
- 返回步骤 2、4 中的所有行 - 包括重复的行。
- 对第 5 步中的行进行排序以消除重复行。
- 从存储的视图 SCHEMA.DeliveryView 或步骤 6 中定义的视图定义已处理。
- 行被排序以便被分组。
- 从存储的视图 SCHEMA 处理视图定义。或如步骤 8 所定义。
- 读取表 SCHEMA.Retailer 中的每一行。
- 步骤 9、10 的结果集被连接(散列)。
- 从存储的视图 SCHEMA 处理视图定义。或由步骤 11 定义。
- 行已合并。
- 行被远程合并。
技术的
Plan Cardinality Distribution
14 MERGE STATEMENT REMOTE ALL_ROWS
Cost: 25 831 Bytes: 1 143 828 3 738
13 MERGE SCHEMA.Retailer ORCL
12 VIEW SCHEMA.
11 HASH JOIN
Cost: 25 831 Bytes: 1 192 422 3 738
9 VIEW SCHEMA.
Cost: 25 803 Bytes: 194 350 7 475
8 SORT GROUP BY
Cost: 25 803 Bytes: 194 350 7 475
7 VIEW VIEW SCHEMA.DeliveryView ORCL
Cost: 25 802 Bytes: 194 350 7 475
6 SORT UNIQUE
Cost: 25 802 Bytes: 134 550 7 475
5 UNION-ALL
2 SORT GROUP BY
Cost: 97 Bytes: 25 362 1 409
1 TABLE ACCESS FULL TABLE SCHEMA.Delivery [Analyzed] ORCL
Cost: 94 Bytes: 210 654 11 703
4 SORT GROUP BY
Cost: 25 705 Bytes: 109 188 6 066
3 TABLE ACCESS FULL TABLE SCHEMA.DeliveryHistory [Analyzed] ORCL
Cost: 16 827 Bytes: 39 333 636 2 185 202
10 TABLE ACCESS FULL TABLE SCHEMA.Retailer [Analyzed] ORCL
Cost: 27 Bytes: 653 390 2 230
更新计划
成本:101,492,字节:272,060
平白的语言
- 读取表 SCHEMA.Retailer 中的每一行。
- 使用索引 SCHEMA.DeliveryHasReturns 检索了一行或多行。索引按升序扫描。
- 使用从索引中获取的 rowid 访问表 SCHEMA.Delivery 中的行。
- 行被排序以便被分组。
- 使用索引 SCHEMA.DeliveryHistoryHasReturns 检索了一行或多行。索引按升序扫描。
- 使用从索引获取的 rowid 访问表 SCHEMA.DeliveryHistory 中的行。
- 行被排序以便被分组。
- 返回步骤 4、7 中的所有行 - 包括重复的行。
- 对第 8 步中的行进行排序以消除重复行。
- 从存储的视图 SCHEMA.DeliveryView 或步骤 9 中定义的视图定义已处理。
- 行被排序以便被分组。
- 从存储的视图 SCHEMA 处理视图定义。或由步骤 11 定义。
- 行已更新。
- 行已远程更新。
技术的
Plan Cardinality Distribution
14 UPDATE STATEMENT REMOTE ALL_ROWS
Cost: 101 492 Bytes: 272 060 1 115
13 UPDATE SCHEMA.Retailer ORCL
1 TABLE ACCESS FULL TABLE SCHEMA.Retailer [Analyzed] ORCL
Cost: 27 Bytes: 272 060 1 115
12 VIEW SCHEMA.
Cost: 90 Bytes: 52 2
11 SORT GROUP BY
Cost: 90 Bytes: 52 2
10 VIEW VIEW SCHEMA.DeliveryView ORCL
Cost: 90 Bytes: 52 2
9 SORT UNIQUE
Cost: 90 Bytes: 36 2
8 UNION-ALL
4 SORT GROUP BY
Cost: 15 Bytes: 18 1
3 TABLE ACCESS BY INDEX ROWID TABLE SCHEMA.Delivery [Analyzed] ORCL
Cost: 14 Bytes: 108 6
2 INDEX RANGE SCAN INDEX SCHEMA.DeliveryHasReturns [Analyzed] ORCL
Cost: 2 12
7 SORT GROUP BY
Cost: 75 Bytes: 18 1
6 TABLE ACCESS BY INDEX ROWID TABLE SCHEMA.DeliveryHistory [Analyzed] ORCL
Cost: 74 Bytes: 4 590 255
5 INDEX RANGE SCAN INDEX SCHEMA.DeliveryHistoryHasReturns [Analyzed] ORCL
Cost: 6 509