Недавно в офисе:
Боб: Я посмотрел на этот медленный вопрос, о котором ты мне говорил вчера, Алиса. Я добавил индексы, которые вы хотели. Теперь все должно быть хорошо
Алиса: Спасибо, Боб. Я быстро проверю… Нет, Боб, все еще медленно, похоже, не сработало
Боб: Ты прав, Алиса! Похоже, что Oracle не получает индекс для вашего запроса, даже если я добавлю
/*+INDEX(...)*/
подсказку. Я не знаю, что пошло не так !?
И так, история продолжается. Алиса расстроена, потому что ее функция не работает вовремя, Боб расстроен, потому что он думает, что Oracle не работает правильно.
Правдивая история!
Боб забыл про Oracle и NULL
Бедный Боб забыл (или не знал), что Oracle не помещает значения NULL в «обычные» индексы . Подумайте об этом таким образом:
CREATE TABLE person ( id NUMBER(38) NOT NULL PRIMARY KEY, first_name VARCHAR2(50) NOT NULL, last_name VARCHAR2(50) NOT NULL, date_of_birth DATE NULL ); CREATE INDEX i_person_dob ON person(date_of_birth);
Теперь Боб считает, что его индекс решает все проблемы, потому что он проверил, работает ли индекс, используя следующий запрос:
SELECT * FROM person WHERE date_of_birth > DATE '1980-01-01';
(конечно, вы вообще не должны SELECT *
)
И план выполнения выглядел хорошо:
---------------------------------------------------- | Id | Operation | Name | ---------------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS BY INDEX ROWID| PERSON | |* 2 | INDEX RANGE SCAN | I_PERSON_DOB | ----------------------------------------------------
Это потому, что предикат Боба не полагается на то, NULL
чтобы быть частью I_PERSON_DOB
индекса. К сожалению, запрос Алисы выглядел больше так (упрощенная версия):
SELECT 1 FROM dual WHERE DATE '1980-01-01' NOT IN ( SELECT date_of_birth FROM person );
Таким образом, по сути, запрос Алисы проверил, была ли у кого-либо дата рождения на данную дату. Ее план выполнения выглядел так:
------------------------------------- | Id | Operation | Name | ------------------------------------- | 0 | SELECT STATEMENT | | |* 1 | FILTER | | | 2 | FAST DUAL | | |* 3 | TABLE ACCESS FULL| PERSON | -------------------------------------
Как видите, ее запрос сделал TABLE ACCESS FULL
операцию, минуя индекс. Зачем? Это просто:
Даже если наше DATE '1980-01-01'
значение находится в индексе или нет, нам все равно придется проверить всю таблицу, чтобы увидеть, NULL
содержится ли в date_of_birth
столбце одно значение . Потому что, если былоNULL
значение, предикат в запросе Алисы никогда не дают или , но .NOT IN
TRUE
FALSE
NULL
Алиса может решить эту проблему с NOT EXISTS
Алиса может решить легко себя, заменив NOT IN
через NOT EXISTS
, предикат , который не страдает от своеобразной трехзначной булевой логики в SQL .
SELECT 1 FROM dual WHERE NOT EXISTS ( SELECT 1 FROM person WHERE date_of_birth = DATE '1980-01-01' );
Этот новый запрос теперь снова дает оптимальный план:
>------------------------------------------ | Id | Operation | Name | ------------------------------------------ | 0 | SELECT STATEMENT | | |* 1 | FILTER | | | 2 | FAST DUAL | | |* 3 | INDEX RANGE SCAN| I_PERSON_DOB | ------------------------------------------
Но проблема все еще существует, потому что то , что может произойти, произойдет , и Алиса должна будет помнить эту проблему для каждого отдельного запроса, который она пишет.
Боб должен просто установить для столбца NOT NULL
Лучшее решение, однако, это просто установить столбец в NOT NULL
:
ALTER TABLE person MODIFY date_of_birth DATE NOT NULL;
С этим ограничением NOT IN
запрос в точности эквивалентен NOT EXISTS
запросу, и Боб и Алиса снова могут быть друзьями.
Вывод: как найти «плохие» столбцы?
Это просто. В следующем полезном запросе перечислены все индексы, в которых есть хотя бы один обнуляемый столбец.
SELECT i.table_name, i.index_name, LISTAGG( LPAD(i.column_position, 2) || ': ' || RPAD(i.column_name , 30) || ' ' || DECODE(t.nullable, 'Y', '(NULL)', '(NOT NULL)'), ', ' ) WITHIN GROUP (ORDER BY i.column_position) AS "NULLABLE columns in indexes" FROM user_ind_columns i JOIN user_tab_cols t ON (t.table_name, t.column_name) = ((i.table_name, i.column_name)) WHERE EXISTS ( SELECT 1 FROM user_tab_cols t WHERE (t.table_name, t.column_name, t.nullable) = ((i.table_name, i.column_name, 'Y' )) ) GROUP BY i.table_name, i.index_name ORDER BY i.index_name ASC;
При запуске против схемы Боба и Алисы приведенный выше запрос дает:
TABLE_NAME | INDEX_NAME | NULLABLE columns in indexes -----------+--------------+---------------------------- PERSON | I_PERSON_DOB | 1: DATE_OF_BIRTH (NULL)
Используйте этот запрос в своей собственной схеме сейчас и просмотрите результаты, тщательно оценивая, действительно ли вам нужно сохранять этот столбец обнуляемым. В 50% случаев вы этого не делаете. Добавив NOT NULL
ограничение, вы можете значительно ускорить ваше приложение!