慢速Oracle Update与Select相比

时间:2017-01-30 15:36:11

标签: sql oracle

当返回相同行的Select语句花费不到20秒时,我们在Oracle 9i中更新表的性能非常慢。当我说速度很慢时,我们让它在一夜之间运行,但仍然没有完成。

我简化了表格和列以说明问题并隐藏名称,因为它们是第三方应用程序中使用的表格(但是,数据通常在内部维护,支持调用很昂贵)。即使在这个简单的例子中,运行超过4小时后更新也没有完成。

CREATE TABLE lookup 
(
  lookup_key       NUMBER(10,0) PRIMARY KEY, 
  lookup_name      VARCHAR2(30)
);

有c。此表中有34,000行

CREATE TABLE products
(
  product_key     NUMBER(10,0) PRIMARY KEY,
  product_val     NUMBER(10,0)
);

有c。此表中有1450万行

Select语句是:

SELECT * FROM lookup
WHERE lookup_name <> 'Redundant'
AND NOT EXISTS (SELECT 1 FROM products
where product_val = lookup_key);

Update语句是:

UPDATE lookup 
SET lookup_name = 'Redundant'
WHERE lookup_name <> 'Redundant'
AND NOT EXISTS (SELECT 1 FROM products
WHERE product_val = lookup_key);

在这个简单的例子和​​现实世界中,两者的执行计划是相同的。所有统计数据都是最新的。

我想我们可以在PL / SQL中做到这一点但肯定它应该足够快以将查询中的所有行(不在产品表中)更新为SQL中的“Redundant”?感谢任何想法或建议。

3 个答案:

答案 0 :(得分:2)

以下是您的示例设置

CREATE TABLE lookup 
as select 
rownum lookup_key, 'xxxxx'||rownum as lookup_name
from dual connect by level <= 34000;

CREATE TABLE products 
as 
with a as
(select rownum i 
from dual connect by level <= 1450000),
b as 
(select /*+ MATERIALIZE */ rownum j 
from dual connect by level <= 10)
select 
i*10 + j + 34000 product_key,  i*10 + j + 34000 as product_val
from a cross join b;

更新提供了此执行计划 - 请注意HASH JOIN ANTI这是此类查询的有效计划。如果您看到其他内容(例如FILTER)这是一个问题,请为每个updatetd行执行FULL TABLE SCAN 一次

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          | 32957 |  1609K|   554   (3)| 00:00:10 |
|*  1 |  HASH JOIN ANTI    |          | 32957 |  1609K|   554   (3)| 00:00:10 |
|*  2 |   TABLE ACCESS FULL| LOOKUP   | 32957 |  1190K|    19   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| PRODUCTS |  1320K|    16M|   530   (2)| 00:00:10 |
-------------------------------------------------------------------------------

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

   1 - access("PRODUCT_VAL"="LOOKUP_KEY")
   2 - filter("LOOKUP_NAME"<>'Redundant')

此处更新需要少于5秒来更新查找表的所有行

SQL> UPDATE lookup
  2  SET lookup_name = 'Redundant'
  3  WHERE lookup_name <> 'Redundant'
  4  AND NOT EXISTS (SELECT 1 FROM products
  5  WHERE product_val = lookup_key);

34000 rows updated.

Elapsed: 00:00:04.97

请注意,如果您期待大量更新,这是防御策略。更新的总费用与大桌上的FULL TABLE SCAN相当。

如果您需要少量更新,您可以从其他答案中建议的索引中获利。

 create index products_idx on products(PRODUCT_VAL);

执行计划更改为NESTED LOOPS ANTI

-----------------------------------------------------------------------------------
| Id  | Operation           | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT    |              |     1 |    32 |    13   (0)| 00:00:01 |
|   1 |  UPDATE             | LOOKUP       |       |       |            |          |
|   2 |   NESTED LOOPS ANTI |              |     1 |    32 |    13   (0)| 00:00:01 |
|*  3 |    TABLE ACCESS FULL| LOOKUP       |     1 |    19 |    11   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN | PRODUCTS_IDX |    14M|   178M|     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------

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

   3 - filter("LOOKUP_NAME"<>'Redundant')
   4 - access("PRODUCT_VAL"="LOOKUP_KEY")

立即更新一行......

SQL> UPDATE lookup
  2  SET lookup_name = 'Redundant'
  3  WHERE lookup_name <> 'Redundant'
  4  AND NOT EXISTS (SELECT 1 FROM products
  5  WHERE product_val = lookup_key);

1 row updated.

Elapsed: 00:00:00.13

...但是经过的时间将随着更新行的数量而线性增加(对于少数人而言,成本将增加前一个执行计划中FTS的成本。

答案 1 :(得分:1)

出于测试目的,我会这样做:

create table lookup2 as select * from lookup

并运行您的更新。

UPDATE lookup2 
SET lookup_name = 'Redundant'
WHERE lookup_name <> 'Redundant'
AND NOT EXISTS (SELECT 1 FROM products
WHERE product_val = lookup_key);

如果速度很快,则意味着减速是由于在lookup_name,触发器,链接行或其他问题表空间上进行索引重建。如果这种情况变得缓慢,则意味着问题本身就是查询,或者存在物理磁盘性能问题(34k行很难让人相信)。

我也想知道这种相关的不存在如何起作用。也许试试:

create table tmp as
SELECT lookup_key FROM lookup
WHERE lookup_name <> 'Redundant'
AND NOT EXISTS (SELECT 1 FROM products
where product_val = lookup_key);

merge into lookup l
using (select * from tmp) t
on (l.lookup_key = t.lookup_key)
when matched then update set lookup_name = 'Redundant'
WHERE lookup_name <> 'Redundant';

drop table tmp;

或者选择只对前100行而不是全部都快?

答案 2 :(得分:0)

PRODUCT_VAL添加索引,以避免扫描每个PRODUCTS的{​​{1}}表