Медленное обновление при использовании Oracle PL / SQL Table - PullRequest
1 голос
/ 13 января 2010

Мы используем таблицу PL / SQL (с именем pTable) для сбора количества идентификаторов, которые необходимо обновить.

Однако утверждение

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 ]

5 голосов
/ 13 января 2010

Вы можете попробовать подсказку кардинальности. Это хорошо, если вы знаете (примерно) количество строк в коллекции.

UPDATE aTable 
SET aColumn = 1 
WHERE id IN (SELECT /*+ cardinality( pt 10 ) */ 
                     COLUMN_VALUE 
              FROM   TABLE (pTable) pt ); 
3 голосов
/ 13 января 2010

Вот другой подход. Создать временную таблицу:

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 голосов
/ 13 января 2010

Плохой план выполнения, вероятно, неизбежен (к сожалению). Статистической информации для таблицы PL / SQL нет, поэтому оптимизатор не может знать, что в ней мало строк. Можно ли использовать подсказки в ОБНОВЛЕНИИ? Если это так, вы можете принудительно использовать индекс таким образом.

1 голос
/ 14 января 2010

Вы можете попробовать добавить предложение ROWNUM <.... В этом тесте ROWNUM <30 меняет план на использование индекса. Конечно, это зависит от того, какой набор значений имеет разумный максимальный размер. </p>

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)|          |
---------------------------------------------------------------------------------------------------
1 голос
/ 13 января 2010

Это помогло оптимизатору использовать «правильный» индекс вместо того, чтобы выполнять полное сканирование полной таблицы:

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

Я не мог применить это решение к более сложным сценариям, но нашел другие обходные пути для них.

0 голосов
/ 13 января 2010

Интересно, не вызовет ли подсказка MATERIALIZE в подвыборке из таблицы PL / SQL форсирование создания временной таблицы и поможет оптимизатору?

UPDATE aTable
SET aColumn = 1
WHERE id IN (SELECT /*+ MATERIALIZE */ COLUMN_VALUE
                     FROM   TABLE (pTable));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...