有两张1.5亿数据量的表,一张表中有字段name,大概有5000万左右为null,另一张表name字段不为null,两张表可通过主键id关联。现在省略去不必要的字段,以及分区等构造两张实验表:
同事给我的sql类似于以下sql,说更新5000多万数据执行两个小时都执行不完,先来看下语句和执行计划:
update p_t set name=(select name from c_t where p_t.id=c_t.id) where p_t.name is null;
我拿到sql一看是update语句,想都没多想直接加并行,但是加了并行发现子查询根本没有用到并行,而且整体cost没有什么变化。我仔细想了想,这种写法 oracle 内部是通过单条跟新的方式进行,从p_t中那出一条记录去c_t中匹配,c_t中采用索引扫描,然后再通过rowid访问表取到name。所以优化器计算后,不会对子查询中的c_t应用并行,因为此时并行会采用全表扫描,效率肯定没有索引扫描快。语句和执行计划如下:
update /*+parallel(p_t,4)*/ p_t set name=(select /*+parallel(c_t,4)*/ name from c_t where p_t.id=c_t.id) where p_t.name is null;
其实子查询中通过id关联实际上类似于相关子查询,想到数据量,如果能够将两张表通过全表扫描,并用hash join关联应该是最快的。这时候想到了update内联视图或者采用merge两种方法。因为实际情况中p_t与c_t关联会产生重复数据,所以不能用update内联视图的方式,最终采用merge方式。实验环境中我采用两种方法进行优化,为了对比准确,没有加并行。虽然这样更新了整个p_t表,但是只扫描了c_t表一次。
SQL> merge into p_t using c_t
2 on (c_t.id=p_t.id)
3 when matched then
update set
p_t.name = nvl2(p_t.name,c_t.name,p_t.name); 4 5
1000000 rows merged.
Elapsed: 00:00:18.77
SQL> alter table p_t add primary key(id);
Table altered.
SQL> alter table c_t add primary key(id);
Table altered.
SQL> update (select p_t.name name1,c_t.name name2,p_t.id pid,c_t.id cid from p_t,c_t where p_t.id=c_t.id) t
2 set name1=nvl2(name1,name2,name1);
1000000 rows updated.
Elapsed: 00:00:20.14
在使用update内联视图优化时,一定要注意必须通过主键等确保惟一性的条件进行关联更新,不然会报错:
ORA-01779: cannot modify a column which maps to a non key-preserved table
回来上网查了下,网上说还可以通过快速游标进行更新,但我觉得肯定没有一条sql语句块。无论pl/sql再怎么优化,本质上无法避免单条更新和引擎切换带来的消耗。实验如下:
SQL> begin
2 for v_i in (select p_t.rowid,c_t.name from p_t,c_t where c_t.id=p_t.id) loop
3 update p_t set name=v_i.name where rowid=v_i.rowid;
end loop;
end; 4 5
6 /
PL/SQL procedure successfully completed.
Elapsed: 00:01:23.05
实际应用中,我将语句改写成了merge,并加入了dml并行和select并行提示,将原来更新两个多小时的sql优化到只跟新了3分钟不到。
同事给我的sql类似于以下sql,说更新5000多万数据执行两个小时都执行不完,先来看下语句和执行计划:
update p_t set name=(select name from c_t where p_t.id=c_t.id) where p_t.name is null;
我拿到sql一看是update语句,想都没多想直接加并行,但是加了并行发现子查询根本没有用到并行,而且整体cost没有什么变化。我仔细想了想,这种写法 oracle 内部是通过单条跟新的方式进行,从p_t中那出一条记录去c_t中匹配,c_t中采用索引扫描,然后再通过rowid访问表取到name。所以优化器计算后,不会对子查询中的c_t应用并行,因为此时并行会采用全表扫描,效率肯定没有索引扫描快。语句和执行计划如下:
update /*+parallel(p_t,4)*/ p_t set name=(select /*+parallel(c_t,4)*/ name from c_t where p_t.id=c_t.id) where p_t.name is null;
其实子查询中通过id关联实际上类似于相关子查询,想到数据量,如果能够将两张表通过全表扫描,并用hash join关联应该是最快的。这时候想到了update内联视图或者采用merge两种方法。因为实际情况中p_t与c_t关联会产生重复数据,所以不能用update内联视图的方式,最终采用merge方式。实验环境中我采用两种方法进行优化,为了对比准确,没有加并行。虽然这样更新了整个p_t表,但是只扫描了c_t表一次。
SQL> merge into p_t using c_t
2 on (c_t.id=p_t.id)
3 when matched then
update set
p_t.name = nvl2(p_t.name,c_t.name,p_t.name); 4 5
1000000 rows merged.
Elapsed: 00:00:18.77
SQL> alter table p_t add primary key(id);
Table altered.
SQL> alter table c_t add primary key(id);
Table altered.
SQL> update (select p_t.name name1,c_t.name name2,p_t.id pid,c_t.id cid from p_t,c_t where p_t.id=c_t.id) t
2 set name1=nvl2(name1,name2,name1);
1000000 rows updated.
Elapsed: 00:00:20.14
在使用update内联视图优化时,一定要注意必须通过主键等确保惟一性的条件进行关联更新,不然会报错:
ORA-01779: cannot modify a column which maps to a non key-preserved table
回来上网查了下,网上说还可以通过快速游标进行更新,但我觉得肯定没有一条sql语句块。无论pl/sql再怎么优化,本质上无法避免单条更新和引擎切换带来的消耗。实验如下:
SQL> begin
2 for v_i in (select p_t.rowid,c_t.name from p_t,c_t where c_t.id=p_t.id) loop
3 update p_t set name=v_i.name where rowid=v_i.rowid;
end loop;
end; 4 5
6 /
PL/SQL procedure successfully completed.
Elapsed: 00:01:23.05
实际应用中,我将语句改写成了merge,并加入了dml并行和select并行提示,将原来更新两个多小时的sql优化到只跟新了3分钟不到。
总结:update的优化如果可以改写成merge或者update内联视图的方式,适用于大数据量更新,避免采用单条更新,防止多次扫描子查询中的表。资源允许情况,可以开启dml和查询并行。update内联视图要注意必须采用主键等能确保唯一条件进行关联,改写时要保持逻辑一致。
转载自:http://www.itpub.net/thread-1875178-1-1.html