Oracle не использует уникальный индекс для длинного ключа

16

У меня есть таблица с 250K строк в моей тестовой базе данных. (В производстве несколько сотен миллионов, мы можем наблюдать ту же проблему.) Таблица имеет строковый идентификатор nvarchar2 (50), а не ноль, с уникальным индексом (это не PK).

Идентификаторы состоят из первой части, имеющей 8 различных значений в моей тестовой базе данных (и около тысячи в производстве), затем знака @ и, наконец, числа длиной от 1 до 6 цифр. Например, может быть 50 тысяч строк, которые начинаются с 'ABCD_BGX1741F_2006_13_20110808.xml @', а за ним следуют 50 тысяч различных номеров.

Когда я запрашиваю одну строку на основе ее идентификатора, количество элементов оценивается как 1, стоимость очень низкая, она работает нормально. Когда я запрашиваю более одной строки с несколькими идентификаторами в выражении IN или выражении OR, оценки индекса совершенно неверны, поэтому используется полное сканирование таблицы. Если я намекаю на индекс с подсказкой, он очень быстрый, полное сканирование таблицы выполняется на порядок медленнее (и намного медленнее в производстве). Так что это проблема оптимизатора.

В качестве теста я продублировал таблицу (в той же схеме + табличное пространство) с точно таким же DDL и точно таким же содержимым. Я воссоздал уникальный индекс в первой таблице для хорошей меры и создал точно такой же индекс в таблице клонов. Я сделал DBMS_STATS.GATHER_SCHEMA_STATS('schemaname',estimate_percent=>100,cascade=>true);. Вы даже можете видеть, что имена индексов являются последовательными. Таким образом, теперь единственное различие между этими двумя таблицами состоит в том, что первая была загружена в произвольном порядке в течение длительного периода времени, когда на диске были разбросаны блоки (в табличном пространстве вместе с несколькими другими большими таблицами), вторая была загружена как одна пакетная. INSERT-SELECT. Кроме этого, я не могу представить никакой разницы. (Исходная таблица была уменьшена с момента последнего большого удаления, и после этого не было ни одного удаления.)

Вот планы запросов для таблицы больных и клонов (строки под черной кистью одинаковы по всей картинке, а также под серой кистью.):

планы запросов

(В этом примере есть 1867 строк, которые начинаются с идентификатора, выделенного черным цветом. 2-строчный запрос дает мощность 1867 * 2, 3-строчный запрос создает мощность 1867 * 3 и т. Д. По совпадению, Oracle, похоже, не заботится о конце идентификаторов.)

Что может вызвать такое поведение? Очевидно, было бы довольно дорого воссоздать таблицу в производстве.

USER_TABLES: http://i.stack.imgur.com/nDWze.jpg USER_INDEXES: http://i.stack.imgur.com/DG9um.jpg Я изменил только имя схемы и табличного пространства. Вы можете видеть, что имена таблиц и индексов такие же, как на скриншоте плана запросов.

fejesjoco
источник

Ответы:

7

(Это отвечает на другой вопрос о том, почему гистограммы отличаются.)

Гистограммы создаются по умолчанию на основе перекоса столбца и того, использовался ли столбец в соответствующем предикате. Копирование DDL и данных недостаточно, информация о рабочей нагрузке также важна.

Согласно Руководству по настройке производительности :

При удалении таблицы информация о рабочей нагрузке, используемая функцией автоматического сбора гистограмм, и сохраненная история статистики, используемая процедурами RESTORE _ * _ STATS, теряются. Без этих данных эти функции не работают должным образом.

Например, вот таблица с искаженными данными, но без гистограммы:

drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
begin
    dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';

HISTOGRAM
---------
NONE

Если выполнить то же самое, но с запросом до сбора статистики, будет сгенерирована гистограмма.

drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
select count(*) from test1 where a = sysdate; --Only new line
begin
    dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';

HISTOGRAM
---------
FREQUENCY
Джон Хеллер
источник
2
Гениально простой пример. Есть ли у вас какие-либо представления о том, почему CBO использовал гистограммы для оценки количества элементов в уникальном сканировании, а не просто предполагал 1?
Джек Дуглас
Благодарность! Я сделал полное воспроизведение с моими данными и запросами в своем блоге: joco.name/2014/01/05/…
fejesjoco
@ Джек, я думаю, это лень. Инженеры Oracle должны были предположить, что статистика уникального индекса будет иметь такое же количество различных значений, что и строки, поэтому предположение о количестве элементов в 1 не является аппаратным, а просто используется из статистики, как в любом другом случае. Также, как правило, гистограммы превосходят простую статистику. Мой случай, кажется, очень особенный из-за только длинных клавиш, но я считаю, что в противном случае это работает довольно хорошо.
fejesjoco
@fejesjoco Я думаю, что объяснение JL более вероятно, так как гистограммы также превзошли бы общую статистику в случае одного поиска (без in), не так ли? Я думаю, что CBO делает предположение о количестве элементов 1, но только в самом простом случае. Я предполагаю, что вы могли бы обойти все это, используя большой, UNION ALLно могут быть другие причины не делать этого, и JL упоминает другие возможные обходные пути в связанном сообщении в блоге.
Джек Дуглас
1
Еще одна небольшая загадка, которую стоит рассмотреть - как эта гистограмма была создана в первую очередь? Похоже, что Oracle считает перекос столбца только в том случае, если в нем есть дубликаты, что, очевидно, не может иметь ваш уникальный столбец. Кто-то намеренно строил эту гистограмму (маловероятно), или кто-то собирал статистику с нерекомендованными method_opt=>'for all indexed columns'?
Джон Хеллер
8

Я нашел решение! Это так красиво, и я действительно узнал много об Oracle.

Одним словом: гистограммы.

Я начал много читать о том, как работает ОО Oracle, и наткнулся на гистограммы. Я не до конца понял, поэтому взглянул на таблицу USER_HISTOGRAMS и вуаля. Там было несколько рядов для больного стола и практически ничего для клонированного стола. Для больной таблицы была одна строка для каждой из 8 различных частей-идентификаторов. И это ключ: они были обрезаны по 32 символа перед знаком @. Как я уже сказал, первая часть клавиш очень повторяющаяся, после знака @ они становятся разными.

Кажется, что гистограммы могут быть более мощными, чем простой факт, что уникальный индекс всегда имеет мощность 0 или 1 для данного значения. Когда я запрашивал 2+ строки, Oracle посмотрел на гистограмму, подумал, что для этой части, идентифицирующей начало, может быть десятки тысяч значений, и это сбило CBO с курса.

Я удалил гистограммы для этого столбца в старой таблице, и проблема исчезла!

Дополнительная информация: https://blogs.oracle.com/optimizer/entry/how_do_i_drop_an_existing_histogram_on_a_column_and_stop_the_auto_stats_gathering_job_from_creating

fejesjoco
источник
2
Я упоминал об этом в нашем чате :) chat.stackexchange.com/transcript/message/12987649#12987649
Philᵀᴹ
Я этого не видел :). Так что единственная странная вещь - почему в первой таблице, а не в клоне были гистограммы, я думал, что collect_schema_stats обновил все, по-видимому, нет.
fejesjoco
6

Я написал по электронной почте Джонатану Льюису об этом и получил очень полезный ответ:

Странность в вычислениях является следствием ограничений на символьные гистограммы, см., В частности:

http://jonathanlewis.wordpress.com/2010/10/13/frequency-histogram-5/ http://jonathanlewis.wordpress.com/2010/10/19/frequency-histograms-6/

Глядя на этот пример, запрос относится к списку IN, а не к одной строке, поэтому мое первоначальное предположение могло бы заключаться в том, что оптимизатор использовал общую стратегию для расчета селективности по нескольким строкам, а не имел специальный фрагмент кода для Список IN по первичному ключу. Полагаю, им было бы не сложно распознать этот случай, но разработчики, вероятно, не посчитали, что это стоит затраченных усилий.

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

Вывод : если у вас есть довольно длинные и похожие строки в столбце, который является хорошим кандидатом для частотной гистограммы (например, очень описательный столбец состояния), тогда у вас есть проблема, если очень редкое значение выглядит идентично очень популярному значение до первых 32 символов. Вы можете обнаружить, что единственным решением является изменение списка допустимых значений (хотя различные стратегии, включающие виртуальные столбцы или функциональные индексы, могут обойти проблему).

Джек Дуглас
источник
К сожалению, гистограммы кажутся малоизвестной функцией, я думаю, это потому, что она слишком глубока для разработчика SQL и большую часть времени они просто работают, но приятно знать, что об этом много ресурсов, я просто не смотрел в правильные места :). Это очень плохо, что Oracle сокращает 32 байта и принимает катастрофические решения на основе этого. К счастью, мне не нужно ничего подправлять, отбрасывание гистограмм - идеальное решение. Ключевые значения уникальны, я всегда ищу 20 значений за раз, он отлично работает только с индексом и является детерминированным. Но я не буду использовать длинные ключи в следующий раз, это точно.
fejesjoco
Гистограммы довольно хорошо известны среди администраторов баз данных;) Мне нравится тот факт, что вы, кажется, стремитесь изучать более глубокие вещи, и действительно думаете, что вам следует прочитать книгу JL, это очень, очень хорошо. CBO, как правило, делает большую работу: всегда будут крайние случаи, которые требуют расследования, но следует иметь в виду, что даже без отсечения оценки всегда являются только оценками.
Джек Дуглас
1
Если вы запускаете обычное задание статистики (например, то, которое Oracle запускает по умолчанию при чистой установке), вы можете обнаружить, что гистограммы появляются снова, возможно, вам придется искать способ предотвратить это (например, LOCK_TABLE_STATS )
Джек Дуглас,
В своем ответе я упомянул пост в блоге, там есть инструкции, как предотвратить гистограммы для столбца.
fejesjoco
1
@ Джек Дуглас, спасибо, что привлекли Дж. Льюиса и сообщили о нем!
Димитр Радулов