提高多连接合并的性能,包括更新

Posted

技术标签:

【中文标题】提高多连接合并的性能,包括更新【英文标题】:Increase performance of multi-join Merge including Update 【发布时间】:2019-11-13 08:18:16 【问题描述】:

我想根据联接更新列。不幸的是,我正在更新和合并的表相当大:87,220,021 行。

这是表格的内容(我正在与自己合并):

ID            ID_VERSION           VALUE_1           VALUE_2    RES_EXPEC
1                 1                   A               NULL        NULL
1                 2                   A               NULL         1
1                 2                   B               NULL        NULL
1                 3                   B               NULL         1
2                 1                   A               NULL        NULL
2                 1                   B               NULL        NULL
2                 1                   B               NULL        NULL

这是我正在运行的代码:

MERGE INTO EXAMP_TAB USING(
     SELECT   ID, ID_VERSION, VALUE_1 FROM EXAMP_TAB) TAB_B
     ON (EXAMP_TAB.ID = TAB_B.ID
         AND EXAMP_TAB.ID_VERSION = (TAB_B.ID_VERSION - 1)
         AND EXAMP_TAB.VALUE_1 = TAB_B.VALUE_1)
WHEN MATCHED THEN
    UPDATE SET EXAMP_TAB.VALUE_2 = 1;

此操作的目的是检查之前版本的ID中是否存在VALUE_1中具有相同值的条目。如果是,则更新 VALUE_2。

很遗憾,此操作需要数小时。我阅读了一些关于索引的内容,但似乎它们对我没有帮助,因为它们不会提高 UPDATE 操作的性能。

我也对不涉及合并语句的操作持开放态度。

【问题讨论】:

您是否考虑过不将该数据存储在附加列中,而是通过在查询中加入来选择它?那种计算出来的数据总是很难维护,以后会很头疼…… 这里的预期输出是什么?因为您的查询不会更新 ID = 2 的任何记录 @ Tejash 是的,如果没有第二个版本的 ID = 2 与 A 和 B 这些行将保持 NULL。这就是我想要的。 @Radagast81 哪些数据以及如何做到这一点:)? (ID, ID_VERSION) = (1,3) -- 它将有前两行,即(1,2),那么需要比较哪一行? -- 如果您能分享预期的输出结果会更好,以便我们为您提供帮助。 【参考方案1】:

您可以在查询中获取该数据而无需更改任何数据:

-- Your sample data:
WITH EXAMP_TAB (ID,ID_VERSION,VALUE_1) as (
select 1, 1, 'A' from dual union all
select 1, 2, 'A' from dual union all        
select 1, 2, 'B' from dual union all
select 1, 3, 'B' from dual union all
select 2, 1, 'A' from dual union all
select 2, 1, 'B' from dual union all 
select 2, 1, 'B' from dual)
-- Select starting from here:
SELECT id, id_version, value_1
     , CASE WHEN EXISTS (SELECT 1 FROM EXAMP_TAB B
                          WHERE B.ID = A.ID
                            AND B.ID_VERSION = A.ID_VERSION - 1
                            AND B.VALUE_1 = A.VALUE_1)
            THEN 1
       END value_2
  FROM EXAMP_TAB A

结果:

ID   ID_VERSION   VALUE_1   VALUE_2
1    1            A         NULL 
1    2            A         1
1    2            B         NULL 
1    3            B         1
2    1            A         NULL 
2    1            B         NULL 
2    1            B         NULL 

该解决方案的好处是您不必担心保持数据是最新的。

【讨论】:

抱歉,老实说,我对 SQL 很不熟悉。我如何概括您的首选?我有 28 000 000 行,我不能在 WITH 命令之后编写 28 000 000 行代码 :) .. 结果偏离了我的预期结果,因为我认为您加入的是“+ 1”而不是“- 1” . 跳过WITH-行,该行仅供我和其他人在我们的数据库中使用您的示例数据。 我已经改变了,但你的合并将做完全相同的事情(标记错误的行),因为你的表 B 的一侧有“-1”而不是表的一侧A. 我很困惑,因为您发布的结果与我的预期结果相同。此外,恐怕查询也需要相当长的时间:( 可能会造成混淆,因为一旦您添加了预期结果,我就更改了查询和结果。如果查询速度很慢,您可以在表中添加一个索引,其中至少包含 IDID_VERSION 甚至可能是 VALUE_1【参考方案2】:

您可以简单地使用更新语句来提高性能,如下所示:

Update EXAMP_TAB E
   SET VALUE_2 = 1 
 WHERE ROWID IN 
       (SELECT E1.ROWID 
          FROM EXAMP_TAB E1
          JOIN EXAMP_TAB E2
            ON E1.ID = E2.ID
           AND E1.ID_VERSION = E2.ID_VERSION - 1
           AND E1.VALUE_1 = E2.VALUE_2)

在这里,IDID_VERSIONVALUE_1 上的索引将提高整体更新操作的性能。

干杯!!

【讨论】:

【参考方案3】:

我的措辞略有不同:

MERGE INTO EXAMP_TAB USING
      (SELECT ID, ID_VERSION, VALUE_1
       FROM EXAMP_TAB
      ) TAB_B
      ON TAB_B.ID = EXAMP_TAB.ID AND
         TAB_B.ID_VERSION = EXAMP_TAB.ID_VERSION + 1 AND
         TAB_B.VALUE_1 = EXAMP_TAB.VALUE_1
WHEN MATCHED THEN
    UPDATE SET EXAMP_TAB.VALUE_2 = 1;

然后,索引可以提供帮助。你想要一个EXAMP_TAB(ID, VALUE_1, ID_VERSION) 的索引。此索引用于此代码中隐含的“连接”。

也就是说,您正在学习的是更新 8900 万行确实效率低下。我建议您在需要时使用查询来获取值。您可以使用窗口函数:

select e.*,
       (case when lag(id_version) over (partition by id, value_1 order by id_version) = id_version - 1
             then 1 else 0
        end) as value_2
from examp_tab;

有了上面的索引,这应该有相当不错的表现了。

【讨论】:

【参考方案4】:

第一步中,您应该计算要更新的行数。如果数字很低(比如千分之一),请查看 索引。如果您必须更新 数百万行 索引访问是不行的

您的merge 语句没有问题,但不幸的是,仅当表在 ID、ID_VERSION、VALUE_1 上具有主键时才有效。根据您在示例数据中的最后两行,情况似乎并非如此。

使用此数据简单测试MERGE 语句

        ID ID_VERSION VALUE_1 VALUE_2   
---------- ---------- ----    ----------
         1          1 A              
         1          2 A              
         1          2 A 

MERGEORA-30926: unable to get a stable set of rows in the source tables 失败,因为它不知道应该更新两行中的哪一行。

如果您想更新两个(所有)这样的行,请使用带有临时表的两步方法。

创建所有要更新的行的临时表

create table to_upd_tm as
select distinct a.ID, a.ID_VERSION, a.VALUE_1
from EXAMP_TAB  a
join EXAMP_TAB  b 
on a.id = b.id and
a.value_1 = b.value_1 and
a.id_version = b.id_version + 1 
;
create unique index to_upd_tm_idx1 on  to_upd_tm(ID, ID_VERSION, VALUE_1);

请注意,选择中的DISTINCT会删除重复项。

临时表上的唯一索引是下一步强制执行键保留表所必需的。

使用可更新的联接视图进行高性能的批量更新

update (
select a.*
from EXAMP_TAB a
join to_upd_tm b
on a.id = b.id and
a.value_1 = b.value_1 and
a.id_version = b.id_version)
set value_2 = 1;

结果

        ID ID_VERSION VALU VALUE_2   
---------- ---------- ---- ----------
         1          1 A              
         1          2 A    1         
         1          2 A    1 

一般来说,对于大容量 UPDATE,您希望使用 HASH JOIN 在两个表上看到带有 FULL TABLE SCAN 的 exceution plan,如下所示

-----------------------------------------------------------------------------------------
| Id  | Operation           | Name      | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT    |           |   642K|    41M|       |  4251   (1)| 00:00:52 |
|   1 |  UPDATE             | EXAMP_TAB |       |       |       |            |          |
|*  2 |   HASH JOIN         |           |   642K|    41M|    25M|  4251   (1)| 00:00:52 |
|   3 |    TABLE ACCESS FULL| TO_UPD_TM |   642K|    18M|       |   329   (2)| 00:00:04 |
|   4 |    TABLE ACCESS FULL| EXAMP_TAB |   857K|    30M|       |   651   (1)| 00:00:08 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("A"."ID"="B"."ID" AND "A"."VALUE_1"="B"."VALUE_1" AND 
              "A"."ID_VERSION"="B"."ID_VERSION")

【讨论】:

以上是关于提高多连接合并的性能,包括更新的主要内容,如果未能解决你的问题,请参考以下文章

sh 将Ubuntu Linux内核更新到4.9包含BBR算法,它可以显着提高TCP连接性能。 (就像服务一样

扩展 MySQL 数据库,提高许多连接的性能

将多线程合并到 C++ 中如何提高性能,为啥?

EF如何更新多对多连接表

C#多线程编程:线程池ThreadPool

让Django支持数据库长连接(可以提高不少性能哦)