Используйте неопубликованные Oracle VARRAY вместо оператора IN - PullRequest
2 голосов
/ 05 августа 2011

Допустим, пользователи имеют 1 - n учетных записей в системе. Когда они запрашивают базу данных, они могут выбрать из m учетных записей, с m between 1 and n. Обычно SQL, сгенерированный для извлечения их данных, выглядит как

SELECT ... FROM ... WHERE account_id IN (?, ?, ..., ?)

Таким образом, в зависимости от количества учетных записей, которые есть у пользователя, это приведет к новому жесткому анализу в Oracle, новому плану выполнения и т. Д. Теперь существует много подобных запросов и, следовательно, много сложных анализирует, и, возможно, кэш курсора / плана будет заполнен довольно рано, что приведет к еще большему количеству сложных анализов.

Вместо этого я мог бы написать что-то вроде этого

-- use any of these
CREATE TYPE numbers AS VARRAY(1000) of NUMBER(38);
CREATE TYPE numbers AS TABLE OF NUMBER(38);

SELECT ... FROM ... WHERE account_id IN (
  SELECT column_value FROM TABLE(?)
)

-- or

SELECT ... FROM ... JOIN (
  SELECT column_value FROM TABLE(?)
) ON column_value = account_id

И используйте JDBC, чтобы связать java.sql.Array (то есть oracle.sql.ARRAY) с единственной переменной связывания. Понятно, что это приведет к меньшему количеству хардсарсов и меньшему количеству курсоров в кеше для функционально эквивалентных запросов Но есть ли что-то вроде общего недостатка производительности или каких-либо других проблем, с которыми я могу столкнуться?

E.g: Работает ли привязка с изменяемым механизмом просмотра аналогичным образом для переменных или вложенных таблиц? Потому что объем данных, связанных с каждой учетной записью, может сильно отличаться.

В этом случае я использую Oracle 11g, но я думаю, что вопрос интересен для любой версии Oracle.

Ответы [ 4 ]

3 голосов
/ 05 августа 2011

Я предлагаю вам попробовать простое старое соединение, как в

SELECT Col1, Col2
FROM   ACCOUNTS ACCT
       TABLE TAB,
WHERE  ACCT.User = :ParamUser
AND    TAB.account_id = ACCT.account_id;

Альтернативой может быть подзапрос таблицы

SELECT Col1, Col2
FROM   (
       SELECT account_id
       FROM   ACCOUNTS
       WHERE  User = :ParamUser
       ) ACCT,
       TABLE TAB
WHERE  TAB.account_id = ACCT.account_id;

или подзапрос where

SELECT Col1, Col2
FROM   TABLE TAB
WHERE  TAB.account_id IN 
       (
       SELECT account_id 
       FROM   ACCOUNTS
       WHERE  User = :ParamUser
       );

Первый должен быть лучше для исполнения, но вам лучше проверить их все с помощью плана объяснения.

2 голосов
/ 05 августа 2011

Глядя на V $ SQL_BIND_CAPTURE в базе данных 10g, у меня есть несколько строк, где тип данных VARRAY или NESTED_TABLE; фактические значения привязки не были зафиксированы. В базе данных 11g есть только одна такая строка, но она также показывает, что значение привязки не фиксируется. Поэтому я подозреваю, что просмотр значений привязки по существу не происходит для пользовательских типов.

По моему опыту, основная проблема, с которой вы сталкиваетесь при использовании вложенных таблиц или массивов таким образом, заключается в том, что оптимизатор не имеет хорошей оценки мощности, которая может привести к созданию плохих планов. Но есть (недокументированный?) CARDINALITY подсказка , которая может оказаться полезной. Проблема в том, что если вы вычислите фактическую мощность вложенной таблицы и включите ее в запрос, вы вернетесь к нескольким различным текстам запроса. Возможно, если вы ожидаете, что большинство или все пользователи будут иметь не более 10 учетных записей, используйте подсказку, чтобы указать, что это будет полезно. Конечно, сначала я бы попробовал это без подсказки, у вас здесь вообще может не быть проблем.

(Я также думаю, что, возможно, ответ Мигеля - правильный путь.)

1 голос
/ 06 августа 2011

Другой вариант - всегда использовать n привязывать переменные в каждом запросе.Используйте null для m+1 до n.

Oracle игнорирует повторяющиеся элементы в expression_list.Ваши запросы будут выполняться так же, и будет меньше сложных разборов.Но будут дополнительные издержки для связывания всех переменных и передачи данных.К сожалению, я понятия не имею, каким будет общее влияние на производительность, вам придется проверить это.

1 голос
/ 05 августа 2011

Это связывание переменного количества элементов в задачу списка, кажется, встречается много в различной форме.Один из вариантов - объединить идентификаторы в строку, разделенную запятыми, и связать ее, а затем использовать небольшую хитрость, чтобы разбить ее на таблицу, к которой вы можете присоединиться, например:

with bound_inlist
  as
  (
  select
    substr(txt,
           instr (txt, ',', 1, level  ) + 1,
           instr (txt, ',', 1, level+1) - instr (txt, ',', 1, level) -1 )
           as token
    from (select ','||:txt||',' txt from dual)
  connect by level <= length(:txt)-length(replace(:txt,',',''))+1
  )
  select *
from bound_inlist a, actual_table b
where a.token = b.token
* 1003будет проблемой, хотя.

Меняется ли план запроса для большего количества учетных записей, т. Е. Будет ли эффективнее переходить от просмотра к полному просмотру таблицы в некоторых случаях, или он является пограничным?Как кто-то другой предположил, вы можете использовать подсказку CARDINALITY, чтобы указать, сколько идентификаторов связаны, следующий тестовый пример доказывает, что это действительно работает:

create table actual_table (id integer, padding varchar2(100));

create unique index actual_table_idx on actual_table(id);

insert into actual_table
select level, 'this is just some padding for '||level
from dual connect by level <= 1000;

explain plan for
with bound_inlist
  as
  (
  select /*+ CARDINALITY(10) */
    substr(txt,
           instr (txt, ',', 1, level  ) + 1,
           instr (txt, ',', 1, level+1) - instr (txt, ',', 1, level) -1 )
           as token
    from (select ','||:txt||',' txt from dual)
  connect by level <= length(:txt)-length(replace(:txt,',',''))+1
  )
  select *
from bound_inlist a, actual_table b
where a.token = b.id;

----------------------------------------------------------------------------------------------------                                                                                                                                                                                                         
| Id  | Operation                       | Name             | Rows  | Bytes | Cost (%CPU)| Time     |                                                                                                                                                                                                         
----------------------------------------------------------------------------------------------------                                                                                                                                                                                                         
|   0 | SELECT STATEMENT                |                  |    10 |   840 |     2   (0)| 00:00:01 |                                                                                                                                                                                                         
|   1 |  NESTED LOOPS                   |                  |       |       |            |          |                                                                                                                                                                                                         
|   2 |   NESTED LOOPS                  |                  |    10 |   840 |     2   (0)| 00:00:01 |                                                                                                                                                                                                         
|   3 |    VIEW                         |                  |    10 |   190 |     2   (0)| 00:00:01 |                                                                                                                                                                                                         
|*  4 |     CONNECT BY WITHOUT FILTERING|                  |       |       |            |          |                                                                                                                                                                                                         
|   5 |      FAST DUAL                  |                  |     1 |       |     2   (0)| 00:00:01 |                                                                                                                                                                                                         
|*  6 |    INDEX UNIQUE SCAN            | ACTUAL_TABLE_IDX |     1 |       |     0   (0)| 00:00:01 |                                                                                                                                                                                                         
|   7 |   TABLE ACCESS BY INDEX ROWID   | ACTUAL_TABLE     |     1 |    65 |     0   (0)| 00:00:01 |                                                                                                                                                                                                         
---------------------------------------------------------------------------------------------------- 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...