MERGE 与 UPDATE 与子查询的性能比较

Mag*_*ken 5 sql oracle

请注意,为了便于阅读,我修改了表/字段名称等。有些原来的名字很混乱。

\n\n

我有三个不同的表:

\n\n
Retailer (Id+Code is a unique key)\n- Id\n- Code\n- LastReturnDate\n- ...\n\nDelivery/DeliveryHistory (combination of Date+RetailerId is unique)\n- Date\n- RetailerId\n- HasReturns\n- ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

Delivery并且DeliveryHistory几乎完全相同。数据会定期移动到历史表中,并且没有可靠的方法可以知道最后一次发生这种情况的时间。一般来说,Delivery 表非常小——通常少于 100,000 行——而历史表通常有数百万行。

\n\n

LastReturnDate我的任务是根据HasReturnsDeliveryDeliveryHistory 中当前最高日期值更新每个零售商的字段。

\n\n

以前这个问题已经通过定义如下的视图来解决:

\n\n
Retailer (Id+Code is a unique key)\n- Id\n- Code\n- LastReturnDate\n- ...\n\nDelivery/DeliveryHistory (combination of Date+RetailerId is unique)\n- Date\n- RetailerId\n- HasReturns\n- ...\n
Run Code Online (Sandbox Code Playgroud)\n\n

以及以下 UPDATE 语句:

\n\n
SELECT Id, Code, MAX(Date) Date\n   FROM Delivery\n   WHERE HasReturns = 1\n   GROUP BY Id, Code\nUNION\nSELECT Id, Code, MAX(Date) Date\n   FROM DeliveryHistory\n   WHERE HasReturns = 1\n   GROUP BY Id, Code\n
Run Code Online (Sandbox Code Playgroud)\n\n

EXISTS 子句防止更新当前值大于新值的字段,但这实际上不是一个重要问题,因为很难看出在正常程序执行期间如何发生这种情况。另请注意该AND Max(Date) IS NOT NULL部分实际上是多余的,因为 DeliveryView 中的 Date 不可能为 null。但 EXISTS 子句似乎实际上略微提高了性能。

\n\n

然而,最近 UPDATE 的性能却很糟糕。在 Retailer 表仅包含 1000-2000 个相关条目的数据库中,UPDATE 的运行时间超过五分钟。请注意,即使我删除整个 EXISTS 子句,它也会执行此操作,即使用以下非常简单的语句:

\n\n
UPDATE Retailer SET LastReturnDate = (\n    SELECT MAX(Date) FROM DeliveryView\n    WHERE Retailer.Id = DeliveryView.Id AND Retailer.Code = DeliveryView.Code)\nWHERE Code = :Code AND EXISTS (\n    SELECT * FROM DeliveryView\n    WHERE Retailer.Id = DeliveryView.Id AND Retailer.Code = DeliveryView.Code\n    HAVING\n        MAX(Date) > LastReturnDate OR\n        (LastReturnDate IS NULL AND MAX(Date) IS NOT NULL))\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此我一直在寻找更好的解决方案。我的第一个想法是创建一个临时表,但过了一会儿我尝试将其写为 MERGE 语句:

\n\n
UPDATE Retailer SET LastReturnDate = (\n    SELECT MAX(Date) FROM DeliveryView\n    WHERE Retailer.Id = DeliveryView.Id AND Retailer.Code = DeliveryView.Code)\nWHERE Code = :Code\n
Run Code Online (Sandbox Code Playgroud)\n\n

这似乎有效,而且比 UPDATE 快一个数量级以上。

\n\n

我有三个问题

\n\n
    \n
  1. 我能否确定这在所有情况下都会与 UPDATE 具有相同的效果(忽略 LastReturnDate 已经大于 MAX(Date) 的边缘情况)?
  2. \n
  3. 为什么速度这么快?
  4. \n
  5. 有更好的解决方案吗?
  6. \n
\n\n

查询计划

\n\n

合并计划

\n\n

成本:25,831,字节:1,143,828

\n\n

平白的语言

\n\n
    \n
  1. 读取表 SCHEMA.Delivery 中的每一行。
  2. \n
  3. 这些行已排序以便分组。
  4. \n
  5. 读取表 SCHEMA.DeliveryHistory 中的每一行。
  6. \n
  7. 这些行已排序以便分组。
  8. \n
  9. 返回步骤 2、4 中的所有行 - 包括重复行。
  10. \n
  11. 对步骤 5 中的行进行排序以消除重复的行。
  12. \n
  13. 已处理视图定义,无论是从存储的视图 SCHEMA.DeliveryView 还是按照步骤 6 定义的。
  14. \n
  15. 这些行已排序以便分组。
  16. \n
  17. 已从存储的视图架构处理视图定义。或如步骤 8 所定义。
  18. \n
  19. 读取表 SCHEMA.Retailer 中的每一行。
  20. \n
  21. 步骤 9、10 的结果集被连接(散列)。
  22. \n
  23. 已从存储的视图架构处理视图定义。或如步骤 11 所定义。
  24. \n
  25. 行被合并。
  26. \n
  27. 行被远程合并。
  28. \n
\n\n

技术的

\n\n
Plan                                            Cardinality Distribution\n14 MERGE STATEMENT REMOTE  ALL_ROWS\nCost: 25\xc2\xa0831  Bytes: 1\xc2\xa0143\xc2\xa0828                                              3\xc2\xa0738   \n    13 MERGE SCHEMA.Retailer ORCL                                           \n        12 VIEW SCHEMA.                                         \n            11 HASH JOIN  \n            Cost: 25\xc2\xa0831  Bytes: 1\xc2\xa0192\xc2\xa0422                                  3\xc2\xa0738   \n                9 VIEW SCHEMA. \n                Cost: 25\xc2\xa0803  Bytes: 194\xc2\xa0350                            7\xc2\xa0475   \n                    8 SORT GROUP BY  \n                    Cost: 25\xc2\xa0803  Bytes: 194\xc2\xa0350                        7\xc2\xa0475   \n                        7 VIEW VIEW SCHEMA.DeliveryView ORCL\n                        Cost: 25\xc2\xa0802  Bytes: 194\xc2\xa0350                    7\xc2\xa0475   \n                            6 SORT UNIQUE  \n                            Cost: 25\xc2\xa0802  Bytes: 134\xc2\xa0550                7\xc2\xa0475   \n                                5 UNION-ALL                 \n                                    2 SORT GROUP BY  \n                                    Cost: 97  Bytes: 25\xc2\xa0362         1\xc2\xa0409   \n                                        1 TABLE ACCESS FULL TABLE SCHEMA.Delivery [Analyzed] ORCL\n                                        Cost: 94  Bytes: 210\xc2\xa0654    11\xc2\xa0703  \n                                    4 SORT GROUP BY  \n                                    Cost: 25\xc2\xa0705  Bytes: 109\xc2\xa0188        6\xc2\xa0066   \n                                        3 TABLE ACCESS FULL TABLE SCHEMA.DeliveryHistory [Analyzed] ORCL\n                                        Cost: 16\xc2\xa0827  Bytes: 39\xc2\xa0333\xc2\xa0636     2\xc2\xa0185\xc2\xa0202   \n                10 TABLE ACCESS FULL TABLE SCHEMA.Retailer [Analyzed] ORCL\n                Cost: 27  Bytes: 653\xc2\xa0390                            2\xc2\xa0230\n
Run Code Online (Sandbox Code Playgroud)\n\n

更新计划

\n\n

成本:101,492,字节:272,060

\n\n

平白的语言

\n\n
    \n
  1. 读取表 SCHEMA.Retailer 中的每一行。
  2. \n
  3. 使用索引 SCHEMA.DeliveryHasReturns 检索一行或多行。按升序扫描索引。
  4. \n
  5. 使用从索引获取的 rowid 来访问表 SCHEMA.Delivery 中的行。
  6. \n
  7. 这些行已排序以便分组。
  8. \n
  9. 使用索引 SCHEMA.DeliveryHistoryHasReturns 检索一行或多行。按升序扫描索引。
  10. \n
  11. 使用从索引获取的 rowid 来访问表 SCHEMA.DeliveryHistory 中的行。
  12. \n
  13. 这些行已排序以便分组。
  14. \n
  15. 返回步骤 4、7 中的所有行 - 包括重复行。
  16. \n
  17. 对步骤 8 中的行进行排序以消除重复的行。
  18. \n
  19. 已处理视图定义,无论是来自存储的视图 SCHEMA.DeliveryView 还是按照步骤 9 定义的。
  20. \n
  21. 这些行已排序以便分组。
  22. \n
  23. 已从存储的视图架构处理视图定义。或如步骤 11 所定义。
  24. \n
  25. 行已更新。
  26. \n
  27. 行被远程更新。
  28. \n
\n\n

技术的

\n\n
Plan                                        Cardinality Distribution\n14 UPDATE STATEMENT REMOTE  ALL_ROWS\nCost: 101\xc2\xa0492  Bytes: 272\xc2\xa0060                                       1\xc2\xa0115   \n    13 UPDATE SCHEMA.Retailer ORCL                                      \n        1 TABLE ACCESS FULL TABLE SCHEMA.Retailer [Analyzed] ORCL\n        Cost: 27  Bytes: 272\xc2\xa0060                                1\xc2\xa0115   \n        12 VIEW SCHEMA. \n        Cost: 90  Bytes: 52                                 2   \n            11 SORT GROUP BY  \n            Cost: 90  Bytes: 52                             2   \n                10 VIEW VIEW SCHEMA.DeliveryView ORCL\n                Cost: 90  Bytes: 52                         2   \n                    9 SORT UNIQUE  \n                    Cost: 90  Bytes: 36                     2   \n                        8 UNION-ALL                     \n                            4 SORT GROUP BY  \n                            Cost: 15  Bytes: 18             1   \n                                3 TABLE ACCESS BY INDEX ROWID TABLE SCHEMA.Delivery [Analyzed] ORCL\n                                Cost: 14  Bytes: 108        6   \n                                    2 INDEX RANGE SCAN INDEX SCHEMA.DeliveryHasReturns [Analyzed] ORCL\n                                    Cost: 2     12  \n                            7 SORT GROUP BY  \n                            Cost: 75  Bytes: 18             1   \n                                6 TABLE ACCESS BY INDEX ROWID TABLE SCHEMA.DeliveryHistory [Analyzed] ORCL\n                                Cost: 74  Bytes: 4\xc2\xa0590          255 \n                                    5 INDEX RANGE SCAN INDEX SCHEMA.DeliveryHistoryHasReturns [Analyzed] ORCL\n                                    Cost: 6     509\n
Run Code Online (Sandbox Code Playgroud)\n