【问题标题】:OR condition in update impacting performance更新影响性能的 OR 条件
【发布时间】:2021-08-15 23:48:58
【问题描述】:

我有以下更新。 Table1 有 800K 行,并在 col3 中建立索引。 Table2 有 50K 行,索引在 col3 中。

--缓慢的方式--

UPDATE table1 t1
SET
    t1.col1 = 'VALUE1'
WHERE
    t1.col2 = 'VALUE2'
    AND EXISTS (
        SELECT
            1
        FROM
            table2 t2
        WHERE
            t2.col3 = t1.col3
            OR t2.col4 = 'VALUE3'
    )

但是,在“OR”条件下(虽然“AND”条件下它很快),在查看两个表时似乎没有按索引进行。是否有解决此问题的方法,以便可以更快地完成更新?

--快捷方式--

UPDATE table1 t1
SET
    t1.col1 = 'VALUE1'
WHERE
    t1.col2 = 'VALUE2'
    AND EXISTS (
        SELECT
            1
        FROM
            table2 t2
        WHERE
            t2.col3 = t1.col3
            AND t2.col4 = 'VALUE3'
    )

【问题讨论】:

  • 如果 table2 的 col4='VALUE3',您确定要更新 table1 中的所有行吗?不检查 table1 和 table2 之间的行是否相关?
  • 您的 fastslow 语句执行两个不同的更新,所以请说明您想要实现的目标。
  • @SayanMalakshinov 不确定我是否在关注你。有什么替代方案?
  • @MarmiteBomber 我想以快速的方式实现我的慢语句。
  • @SayanMalakshinov 如果有匹配的 col3 并且 t2.col4 是 'VALUE3',我想更新 t1。

标签: sql oracle query-optimization


【解决方案1】:

从您的公式可以推断出您尝试仅更新一行(或可能的几行)并且您希望快速执行它。

因此,首先认为删除的是table1.col2 上的缺少索引,显然需要使用谓词执行索引访问表:

WHERE
    t1.col2 = 'VALUE2'

与子查询类似,您需要在table2.col4 上建立索引(对于谓词t2.col4 = 'VALUE3'

使用此索引设置,您原来的 slow 查询将执行良好(前提是 t1.col2 = 'VALUE2' 只返回几行)。 无需重写花蕊

你可以检查一下你应该期待的执行计划

-----------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT                      |             |     1 |    27 |     4   (0)| 00:00:01 |
|   1 |  UPDATE                               | TABLE1      |       |       |            |          |
|*  2 |   FILTER                              |             |       |       |            |          |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| TABLE1      |     1 |    27 |     2   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN                  | TABLE1_IDX2 |     1 |       |     1   (0)| 00:00:01 |
|   5 |    TABLE ACCESS BY INDEX ROWID BATCHED| TABLE2      |     1 |    27 |     2   (0)| 00:00:01 |
|   6 |     BITMAP CONVERSION TO ROWIDS       |             |       |       |            |          |
|   7 |      BITMAP OR                        |             |       |       |            |          |
|   8 |       BITMAP CONVERSION FROM ROWIDS   |             |       |       |            |          |
|*  9 |        INDEX RANGE SCAN               | TABLE2_IDX  |       |       |     1   (0)| 00:00:01 |
|  10 |       BITMAP CONVERSION FROM ROWIDS   |             |       |       |            |          |
|* 11 |        INDEX RANGE SCAN               | TABLE2_IDX2 |       |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - filter( EXISTS (SELECT 0 FROM "TABLE2" "T2" WHERE "T2"."COL3"=:B1 OR 
              "T2"."COL4"='VALUE3'))
   4 - access("T1"."COL2"='VALUE2')
   9 - access("T2"."COL3"=:B1)
  11 - access("T2"."COL4"='VALUE3')

所以基本上更新是通过一个索引范围扫描,结合FILTER 检查exists 子查询实现为两个索引范围扫描结合BITMAP OR

大型更新的替代解决方案

如果您遇到相反的情况并且谓词 t1.col2 = 'VALUE2' 返回大量行(例如 100K 或更多),您将 1) 绝对不想使用索引访问进行更新(FULL TABLE SCAN 会更有效) 和 2) 您应该将语句拆分在两个更新中。

如果table2包含一行col4 = 'VALUE3'执行

UPDATE table1 t1
SET
    t1.col1 = 'VALUE1'
WHERE
    t1.col2 = 'VALUE2'

否则执行

UPDATE table1 t1
SET
    t1.col1 = 'VALUE1'
WHERE
    t1.col2 = 'VALUE2'
    AND EXISTS (
        SELECT
            1
        FROM
            table2 t2
        WHERE
            t2.col3 = t1.col3)

split 的原因很简单,第一条语句将使用VALUE2(大数字)更新所有行,并且您希望部署FULL TABLE SCAN

在第二种情况下,最多更新 30K 行(table2 的基数),您可以使用与上述类似的 FILTERed 执行计划。

一条语句涵盖两个非常不同的星座时,这是一个典型问题,因此所选的执行计划在一种情况下工作良好,另一种情况下工作不好。

如果你使用两种说法,一种针对每种情况的说法,你就可以避免问题。

【讨论】:

  • 非常感谢。那么,如果 t1.col2 = 'VALUE2' 返回的行数多于几行,我该怎么办?
  • 更新了答案,明白这是你的问题。 @jeiv
【解决方案2】:

将更新语句拆分为两个语句

出于性能考虑,请将 UPDATE 拆分为两个语句。

UPDATE table1 t1
   SET t1.col1 = 'VALUE1'
 WHERE t1.col2 = 'VALUE2'
   AND EXISTS (
        SELECT 1
          FROM table2 t2
 WHERE t2.col4 = 'VALUE3'
       ) ;


UPDATE table1 t1
   SET t1.col1 = 'VALUE1'
 WHERE t1.col2 = 'VALUE2'
   AND EXISTS (
        SELECT 1
          FROM table2 t2
 WHERE t2.col3 = t1.col3
       );
    

为什么会有帮助?

-在某些情况下,具有类似于笛卡尔连接的连接的相关子查询正在自找麻烦。

-将逻辑提炼成两个应该具有更好性能的 UPDATE 语句。


如果您知道 table2 的数据集至少包含一条记录 其中col4 = 'VALUE3'(也许总是会),您可以进一步优化它(如 Marmite Bomber 所示)。

-为第一个更新语句完全删除相关子查询。

-甚至不需要执行第二个更新语句。受影响的记录结果集将是第一次更新语句的子集。

【讨论】:

  • 你也可以优化,如果第一个语句更新了一些行,你不需要执行第二个更新(因为已经更新了整个 tabel1)。
  • 我修改了答案以考虑您的想法并同意还有更多优化的机会,特别是如果这是在 PLSQL 块中。
  • @PatrickBacon 不确定我是否理解正确。如果 table2 至少有一个 t2.col4 = 'VALUE3' 的条目,第一次更新不会更新所有 table1 吗?
  • 嗨@jeiv,在所有情况下,t1.col2 = 'VALUE2' 必须为真才能进行更新。
  • @PatrickBacon 哦,好吧,我明白了。我刚刚意识到我应该以不同的方式问这个问题。我会发布另一个问题。
【解决方案3】:

你可以尝试使用两个exists:

WHERE t1.col2 = 'VALUE2' AND
      ( EXISTS (SELECT 1
                FROM table2 t2
                WHERE t2.col3 = t1.col3
               ) OR
        EXISTS (SELECT 1
                FROM table2 t2
                WHERE t2.col4 = 'VALUE3'
               )
      )

为了这两个效果最好,您需要在 table2(col3)table2(col4) 上建立索引。

【讨论】:

  • 如果第一个条件也是 OR,那会起作用吗?与问题无关,只是想知道。
  • 不确定我是否理解正确。如果 table2 至少有一个 t2.col4 = 'VALUE3' 的条目,第二个语句是否会更新所有 table1?
  • @Jeiv 。 . .是的。这就是您问题中的代码的作用。
猜你喜欢
  • 2013-11-04
  • 1970-01-01
  • 2016-02-04
  • 1970-01-01
  • 1970-01-01
  • 2015-03-03
  • 2020-07-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多