Это будет долго, так вот краткое резюме, чтобы привлечь вас: мой топ-N запрос с COUNT STOPKEY
и ORDER BY STOPKEY
в его плане является еще медленно без уважительной причины.топ-N запрос делает слишком много работы, несмотря на оптимизацию STOPKEY
Теперь детали. Он начинается с медленной функции. В реальной жизни это включает в себя строковые манипуляции с регулярными выражениями. Для демонстрационных целей здесь преднамеренно глупый рекурсивный алгоритм Фибоначчи. Я считаю, что быть довольно быстро для входов примерно до 25, медленно около 30, и смешно на 35.
-- I repeat: Please no advice on how to do Fibonacci correctly.
-- This is slow on purpose!
CREATE OR REPLACE FUNCTION tmp_fib (
n INTEGER
)
RETURN INTEGER
AS
BEGIN
IF n = 0 OR n = 1 THEN
RETURN 1;
END IF;
RETURN tmp_fib(n-2) + tmp_fib(n-1);
END;
/
Теперь некоторые ввода: список имен и номеров.
CREATE TABLE tmp_table (
name VARCHAR2(20) UNIQUE NOT NULL,
num NUMBER(2,0)
);
INSERT INTO tmp_table (name,num)
SELECT 'Alpha', 10 FROM dual UNION ALL
SELECT 'Bravo', 11 FROM dual UNION ALL
SELECT 'Charlie', 33 FROM dual;
Вот пример медленного запроса: использовать медленные функции Фибоначчи выбора строк, чьи Num генерирует число Фибоначчи с удвоенным цифрой.
SELECT p.name, p.num
FROM tmp_table p
WHERE REGEXP_LIKE(tmp_fib(p.num), '(.)\1')
ORDER BY p.name;
Это верно для 11 и 33, так и Bravo
Charlie
находятся в выходе. Это займет около 5 секунд для запуска, почти все из которых являются медленными расчет tmp_fib(33)
. Поэтому я хочу сделать более быструю версию медленного запроса , преобразов его в запрос top-N. При N = 1, это выглядит так:
SELECT * FROM (
SELECT p.name, p.num
FROM tmp_table p
WHERE REGEXP_LIKE(tmp_fib(p.num), '(.)\1')
ORDER BY p.name
)
WHERE ROWNUM <= 1;
И теперь она возвращает верхний результат, Bravo
. Но все равно требуется 5 секунд для запуска! Единственное объяснение состоит в том, что он все еще вычисляет tmp_fib(33)
, хотя результат этого расчета не имеет значения . Он должен был принять решение о выходе Bravo
, поэтому нет необходимости проверять условие WHERE для остальной таблицы .
Я думал, что, возможно, оптимизатору просто нужно сказать, что tmp_fib
стоит дорого. Так что я пытался сказать ему, что, как это:
ASSOCIATE STATISTICS WITH FUNCTIONS tmp_fib DEFAULT COST (999999999,0,0);
Это изменяет некоторые цифры затрат в плане, но это не делает вводного запрос быстрее.
Выход SELECT * FROM v$version
в случае, если это версия-зависимый:
Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - 64bit Production
PL/SQL Release 11.2.0.2.0 - Production
CORE 11.2.0.2.0 Production
TNS for 64-bit Windows: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production
И вот автотрассировка запроса топ-1. Похоже, что запрашивает , что запрос занял 1 секунду, но это неверно. Он пробежал около 5 секунд.
NAME NUM
-------------------- ----------
Bravo 11
Execution Plan
----------------------------------------------------------
Plan hash value: 548796432
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 55 | 4 (25)| 00:00:01 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 1 | 55 | 4 (25)| 00:00:01 |
|* 3 | SORT ORDER BY STOPKEY| | 1 | 55 | 4 (25)| 00:00:01 |
|* 4 | TABLE ACCESS FULL | TMP_TABLE | 1 | 55 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=1)
3 - filter(ROWNUM<=1)
4 - filter(REGEXP_LIKE (TO_CHAR("TMP_FIB"("P"."NUM")),'(.)\1'))
Note
-----
- dynamic sampling used for this statement (level=2)
Statistics
----------------------------------------------------------
27 recursive calls
0 db block gets
25 consistent gets
0 physical reads
0 redo size
593 bytes sent via SQL*Net to client
524 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
ОБНОВЛЯТЬ: Как я уже говорил в комментариях, INDEX
подсказка помогает этот запрос много. Было бы достаточно, чтобы вас приняли в качестве правильного ответа, хотя это не очень хорошо отразилось на моем реальном сценарии.И в ироническом твисте, Oracle, похоже, учился на опыте, и теперь выбирает план INDEX
по умолчанию; Я должен сказать это NO_INDEX
, чтобы воспроизвести оригинальное медленное поведение.
В реальном сценарии я применил более сложное решение, переписывая запрос как функцию PL/SQL. Вот как моя техника выглядит, примененные к fib
проблемам:
CREATE OR REPLACE PACKAGE tmp_package IS
TYPE t_namenum IS TABLE OF tmp_table%ROWTYPE;
FUNCTION get_interesting_names (howmany INTEGER) RETURN t_namenum PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY tmp_package IS
FUNCTION get_interesting_names (howmany INTEGER) RETURN t_namenum PIPELINED IS
CURSOR c IS SELECT name, num FROM tmp_table ORDER BY name;
rec c%ROWTYPE;
outcount INTEGER;
BEGIN
OPEN c;
outcount := 0;
WHILE outcount < howmany LOOP
FETCH c INTO rec;
EXIT WHEN c%NOTFOUND;
IF REGEXP_LIKE(tmp_fib(rec.num), '(.)\1') THEN
PIPE ROW(rec);
outcount := outcount + 1;
END IF;
END LOOP;
END;
END;
/
SELECT * FROM TABLE(tmp_package.get_interesting_names(1));
Благодаря ответившим, которые читают этот вопрос и побежали испытание и помогли мне понять планы выполнения, и я буду распоряжаться этим вопрос однако они предлагают.
Обратите внимание, где встречается 'filter' - _before_. Вы не можете предположить, что '11' - это первая строка, которая будет проверяться на достоверность. – Mat
@ Мат, так вот мой вопрос: как я могу убедить оракул сделать фильтр после сортировки? –
Не оценивая предложение 'where' для каждой строки таблицы, как он узнает, что' Bravo' не будет отфильтрован? –