使用Oracle PL / SQL表时缓慢更新

时间:2010-01-13 13:53:05

标签: oracle plsql

我们正在使用PL / SQL表(名为pTable)来收集许多要更新的ID。

然而,声明

UPDATE aTable
SET aColumn = 1
WHERE id IN (SELECT COLUMN_VALUE
                     FROM   TABLE (pTable));

需要很长时间才能执行。

似乎优化器提出了一个非常糟糕的执行计划,而不是使用在id(作为主键)上定义的索引,它决定在aTable上使用全表扫描。 pTable通常包含很少的值(在大多数情况下只有一个)。

我们可以做些什么来加快速度?我们提出的最好的是处理低pTable.Count(1和2)作为特殊情况,但这当然不是很优雅。

感谢所有伟大的建议。我在http://smartercoding.blogspot.com/2010/01/performance-issues-using-plsql-tables.html的博客中写过这个问题。

6 个答案:

答案 0 :(得分:5)

您可以尝试基数提示。如果您(大致)知道集合中的行数,这是很好的。

UPDATE aTable 
SET aColumn = 1 
WHERE id IN (SELECT /*+ cardinality( pt 10 ) */ 
                     COLUMN_VALUE 
              FROM   TABLE (pTable) pt ); 

答案 1 :(得分:3)

这是另一种方法。创建一个临时表:

create global temporary table pTempTable ( id int primary key )
    on commit delete rows;

要执行更新,请使用pTempTable的内容填充pTable并执行:

update
(
    select aColumn
    from aTable aa join pTempTable pp on aa.id = pp.id
)
set aColumn = 1;

如果不采用优化器提示,应该可以很好地执行。

答案 2 :(得分:2)

糟糕的执行计划可能是不可避免的(不幸的是)。 PL / SQL表没有统计信息,因此优化器无法知道其中的行数很少。是否可以在UPDATE中使用提示?如果是这样,您可能会强制使用索引。

答案 3 :(得分:1)

它有助于告诉优化器使用“正确”索引而不是进行疯狂的全表扫描:

UPDATE /*+ INDEX(aTable PK_aTable) */aTable
SET aColumn = 1
WHERE id IN (SELECT COLUMN_VALUE
                  FROM   TABLE (CAST (pdarllist AS list_of_keys)));

我无法将此解决方案应用于更复杂的方案,但找到了其他解决方法。

答案 4 :(得分:1)

您可以尝试添加ROWNUM< ......条款。 在该测试中,ROWNUM< 30更改计划以使用索引。 当然,这取决于具有合理最大尺寸的值集。

create table atable (acolumn number, id number);
insert into atable select rownum, rownum from dual connect by level < 150000;
alter table atable add constraint atab_pk primary key (id);

exec dbms_stats.gather_table_stats(ownname => user, tabname => 'ATABLE');

create type type_coll is table of number(4);
/

declare
    v_coll type_coll;
begin
  v_coll := type_coll(1,2,3,4);
  UPDATE aTable
  SET aColumn = 1
  WHERE id IN (SELECT COLUMN_VALUE
                     FROM   TABLE (v_coll));
end;
/

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
UPDATE ATABLE SET ACOLUMN = 1 WHERE ID IN (SELECT COLUMN_VALUE FROM TABLE (:B1 ))
----------------------------------------------------------------------------------------------
| Id  | Operation                           | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT                    |        |       |       |   142 (100)|          |
|   1 |  UPDATE                             | ATABLE |       |       |            |          |
|*  2 |   HASH JOIN RIGHT SEMI              |        |     1 |    11 |   142   (8)| 00:00:02 |
|   3 |    COLLECTION ITERATOR PICKLER FETCH|        |       |       |            |          |
|   4 |    TABLE ACCESS FULL                | ATABLE |   150K|  1325K|   108   (6)| 00:00:02 |
----------------------------------------------------------------------------------------------

declare
    v_coll type_coll;
begin
  v_coll := type_coll(1,2,3,4);
  UPDATE aTable
  SET aColumn = 1
  WHERE id IN (SELECT COLUMN_VALUE
                     FROM   TABLE (v_coll)
                            where rownum < 30);
end;
/


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------
UPDATE ATABLE SET ACOLUMN = 1 WHERE ID IN (SELECT COLUMN_VALUE FROM TABLE (:B1 ) WHERE
ROWNUM < 30)

---------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT                       |          |       |       |    31 (100)|          |
|   1 |  UPDATE                                | ATABLE   |       |       |            |          |
|   2 |   NESTED LOOPS                         |          |     1 |    22 |    31   (4)| 00:00:01 |
|   3 |    VIEW                                | VW_NSO_1 |    29 |   377 |    29   (0)| 00:00:01 |
|   4 |     SORT UNIQUE                        |          |     1 |    58 |            |          |
|*  5 |      COUNT STOPKEY                     |          |       |       |            |          |
|   6 |       COLLECTION ITERATOR PICKLER FETCH|          |       |       |            |          |
|*  7 |    INDEX UNIQUE SCAN                   | ATAB_PK  |     1 |     9 |     0   (0)|          |
---------------------------------------------------------------------------------------------------

答案 5 :(得分:0)

我想知道来自PL / SQL表的subselect中的MATERIALIZE提示是否会强制临时表实例化并帮助优化器?

UPDATE aTable
SET aColumn = 1
WHERE id IN (SELECT /*+ MATERIALIZE */ COLUMN_VALUE
                     FROM   TABLE (pTable));