【问题标题】:Is there a way to improve a MERGE query?有没有办法改进 MERGE 查询?
【发布时间】:2021-04-19 11:43:58
【问题描述】:

我使用此查询向我的表中插入新条目

MERGE INTO CLEAN clean USING DUAL ON (clean.id = :id)
WHEN NOT MATCHED THEN INSERT (ID, COUNT) VALUES (:id, :xcount)
WHEN MATCHED THEN UPDATE SET clean.COUNT = clean.count + :xcount

看来我inserts 比updates 做得更多,有没有办法提高我目前的表现?

我将 cx_Oracle 与 Python 3 和 OracleDB 19c 一起使用。

【问题讨论】:

  • 您的问题不是由 MERGE 语句引起的,而是由于您在 在循环中调用它 - 即所谓的 row by row aka慢慢来。您必须将其更改为调用 one MERGE 语句,在其中将 dual 替换为包含要处理的所有行的 临时表
  • @MarmiteBomber,你能解释更多吗?我不太明白
  • 嗯,有一个很大的区别是你调用 one MERGE 来处理 1M 行(= 快速)或一 百万 次 @987654328 @ 语句处理一行(=慢得多,你的情况)。
  • @MarmiteBomber 有没有办法从 python 做到这一点?我只是在使用 execute_many
  • 肯定有办法,把整个语句贴出来,这样逻辑就可见了……

标签: python oracle


【解决方案1】:

如果您的方法遇到大量 问题,您很可能在clean.id 列上缺少索引,当MERGE 使用dual 时,这是您的方法所必需的作为每一行的来源。

当您说id 是一个主键时,这不太可能。

所以基本上你的想法是正确的,你会看到execution plan类似于下面的:

---------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------
|   0 | MERGE STATEMENT                 |                 |       |       |     2 (100)|          |
|   1 |  MERGE                          | CLEAN           |       |       |            |          |
|   2 |   VIEW                          |                 |       |       |            |          |
|   3 |    NESTED LOOPS OUTER           |                 |     1 |    40 |     2   (0)| 00:00:01 |
|   4 |     TABLE ACCESS FULL           | DUAL            |     1 |     2 |     2   (0)| 00:00:01 |
|   5 |     VIEW                        | VW_LAT_A18161FF |     1 |    38 |     0   (0)|          |
|   6 |      TABLE ACCESS BY INDEX ROWID| CLEAN           |     1 |    38 |     0   (0)|          |
|*  7 |       INDEX UNIQUE SCAN         | CLEAN_UX1       |     1 |       |     0   (0)|          |
---------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   7 - access("CLEAN"."ID"=:ID)

所以执行计划很好并且有效,但是它有一个问题。

请记住始终使用索引,在处理几行时您会很高兴,但它不会扩展

如果您正在处理 数百万 条记录,您可能会退回到两步处理,

  • 在临时表中插入所有行

  • 使用临时表执行单个MERGE 语句

最大的优势是 Oracle 可以打开一个hash join 并摆脱对每一百万行的索引访问。

这里是一个测试 clean 表的示例,该表使用 1M id(未显示)启动并执行 1M 插入和 1M 更新:

n  = 1000000
data2 = [{"id" : i, "xcount" :1} for i in range(2*n)]  

sql3 = """
    insert into tmp (id,count)
    values (:id,:xcount)"""
sql4 = """MERGE into clean USING tmp on (clean.id = tmp.id)
          when not matched then insert (id, count)  values (tmp.id, tmp.count)
          when matched then update set clean.count= clean.count + tmp.count"""    

cursor.executemany(sql3, data2)
cursor.execute(sql4)

测试大约运行。 10 秒,不到一半的人使用MERGE使用dual

如果这还不够,您将不得不使用并行选项

【讨论】:

  • 很高兴能够帮助@john
【解决方案2】:

MERGE 相当快。插入比更新快,我会说(通常)。

所以,如果您要问如何使插入更快,那就要看情况了。

  • 如果您一次插入一行,则不应该有任何瓶颈。
  • 如果您要插入数百万行,请查看表上是否启用了触发每一行的触发器并执行某些操作(减慢进程)。

截至更新,clean.id 列是否有索引?如果没有,它可能会有所帮助。

否则,请看解释计划的内容;定期收集统计数据。

【讨论】:

  • clean.id 是主键,没有任何触发器。如何从 python 中获得解释?
  • 我不知道;它比 Python 更接近 Oracle,我从例如SQL Developer 或 TOAD(甚至 SQL*Plus)——换句话说,我用来访问我的 Oracle 数据库的工具。由于 ID 是主键,这也意味着该列上也有一个唯一索引 - 是的,它已被索引。
  • 顺便说一句,@Marmite Bomber 在他们的回答中很好地解释了这一点。
猜你喜欢
  • 1970-01-01
  • 2012-12-12
  • 1970-01-01
  • 2020-03-25
  • 1970-01-01
  • 1970-01-01
  • 2018-08-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多