Я разрабатываю DWH для Oracle 11g. У нас есть несколько больших таблиц (250+ миллионов строк), разделенных по значению. Каждый раздел назначается другому источнику питания, и каждый раздел не зависит от других, поэтому их можно загружать и обрабатывать одновременно.
Распределение данных очень неравномерно, у нас есть раздел с миллионами строк и разделы с не более чем сотней строк, но я не выбрал схему разбиения и, кстати, не могу ее изменить.
Учитывая объем данных, мы должны заверить, что каждый раздел всегда имеет актуальную статистику, потому что, если последующие разработки не имеют оптимального доступа к данным, они будут длиться вечно.
Итак, для каждого параллельного потока ETL мы
- Усечение раздела
- Загрузка данных из промежуточной области с помощью
SELECT /*+ APPEND */ INTO big_table PARTITION(part1) FROM temp_table WHERE partition_colum = PART1
(у нас есть прямой путь, и мы не блокируем всю таблицу)
- Мы собираем статистику для модифицированного раздела.
На первом этапе проекта мы использовали стратегию APPROX_GLOBAL_AND_PARTITION
и работали как шарм
dbms_stats.gather_table_stats(ownname=>myschema,
tabname=>big_table,
partname=>part1,
estimate_percent=>1,
granularity=>'APPROX_GLOBAL_AND_PARTITION',
CASCADE=>dbms_stats.auto_cascade,
degree=>dbms_stats.auto_degree)
Но у нас был тот недостаток, что при загрузке небольшого раздела часть APPROX_GLOBAL была доминирующей (все же намного быстрее GLOBAL), а для небольшого раздела у нас было, например, 10 секунд загрузки и 20 минут статистики.
Итак, нам предложили переключиться на INCREMENTAL STATS функцию 11g, а это значит, что вы не указали раздел, который вы изменили, вы оставите все параметры в auto, и Oracle делает это магия, автоматически понимая, какие разделы (разделы) были затронуты. И это действительно работает, мы действительно ускорили небольшой раздел. После включения функции вызов стал
dbms_stats.gather_table_stats(ownname=>myschema,
tabname=>big_table,
estimate_percent=>dbms_stats.auto_sample_size,
granularity=>'AUTO',
CASCADE=>dbms_stats.auto_cascade,
degree=>dbms_stats.auto_degree)
сообщите, что вы больше не пропускаете раздел, и вы не указываете примерный процент.
Но у нас есть недостаток, может быть, еще хуже, чем предыдущий, и это коррелирует с высоким уровнем parallelism, который у нас есть.
Предположим, что у нас есть 2 больших раздела, которые запускаются в одно и то же время, они закончат фазу загрузки почти одновременно.
-
Первый поток завершает инструкцию insert, фиксирует и запускает сбор статистических данных. В процедуре статистики есть два измененных раздела (это правильно, один заполнен, а второй усечен, с текущей транзакцией), корректно обновляет статистику для обоих разделов.
-
В конце концов второй раздел заканчивается, собирает статистику, он видит, что все разделы уже обновлены, и ничего не делает (это НЕ правильно, потому что второй поток передал данные тем временем).
Результат:
PARTITION NAME | LAST ANALYZED | NUM ROWS | BLOCKS | SAMPLE SIZE
-----------------------------------------------------------------------
PART1 | 04-MAR-2015 15:40:42 | 805731 | 20314 | 805731
PART2 | 04-MAR-2015 15:41:48 | 0 | 16234 | (null)
и следствие состоит в том, что я иногда подвергаюсь не оптимальным планам (что означает убить сессию, обновлять вручную статистику, снова запускать прецессию вручную).
Я попробовал даже поставить эксклюзивный замок на сбор, поэтому не более одного потока можно сразу собрать статистику по одной и той же таблице, но ничего не изменилось.
ИМХО это странное поведение, потому что процедура статистики, во второй раз, когда она вызывается, должна проверять последнюю фиксацию на втором разделе и должна видеть ее более новой, чем последнее время сбора статистики. Но похоже, что этого не происходит.
Я что-то делаю неправильно? Это ошибка Oracle? Как я могу гарантировать, что все статистические данные всегда обновляются с включенной функцией инкрементной статистики, а высокий уровень concurrency?