当返回相同行的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”?感谢任何想法或建议。
答案 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}}表