Подтвердить что ты не робот

Параллельная сбор статистики по таблице разделенных Oracle 11g

Я разрабатываю DWH для Oracle 11g. У нас есть несколько больших таблиц (250+ миллионов строк), разделенных по значению. Каждый раздел назначается другому источнику питания, и каждый раздел не зависит от других, поэтому их можно загружать и обрабатывать одновременно.

Распределение данных очень неравномерно, у нас есть раздел с миллионами строк и разделы с не более чем сотней строк, но я не выбрал схему разбиения и, кстати, не могу ее изменить.

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

Итак, для каждого параллельного потока ETL мы

  • Усечение раздела
  • Загрузка данных из промежуточной области с помощью

SELECT /*+ APPEND */ INTO big_table PARTITION(part1) FROM temp_table WHERE partition_colum = PART1

(у нас есть прямой путь, и мы не блокируем всю таблицу)

  1. Мы собираем статистику для модифицированного раздела.

На первом этапе проекта мы использовали стратегию 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?

4b9b3361

Ответ 1

Мне удалось достичь достойного компромисса с этой функцией.

PROCEDURE gather_tb_partiz(
    p_tblname IN VARCHAR2,
    p_partname IN VARCHAR2)
IS
  v_stale all_tab_statistics.stale_stats%TYPE;
BEGIN
  BEGIN
    SELECT stale_stats
    INTO v_stale
    FROM user_tab_statistics
    WHERE table_name = p_tblname
    AND object_type = 'TABLE';
  EXCEPTION
  WHEN NO_DATA_FOUND THEN
    v_stale := 'YES';
  END;
  IF v_stale = 'YES' THEN
    dbms_stats.gather_table_stats(ownname=>myschema, 
                                  tabname=> p_tblname,
                                  partname=>p_partname,
                                  degree=>dbms_stats.auto_degree,
                                  granularity=>'APPROX_GLOBAL AND PARTITION') ;
  ELSE
    dbms_stats.gather_table_stats(ownname=>myschema,
                                 tabname=>p_tblname,
                                 partname=>p_partname,
                                 degree=>dbms_stats.auto_degree,
                                 granularity=>'PARTITION') ;
  END IF;
END gather_tb_partiz;

В конце каждого ETL, если количество добавленных/удаленных/модифицированных строк достаточно низкое, чтобы не пометить таблицу как устаревшую (по умолчанию 10%, можно настроить с помощью параметра STALE_PERCENT), я собираю только статистику разделов; в противном случае я собираю глобальную статистику и разделы.

Это ускоряет ETL небольшого раздела, поскольку глобальный раздел не должен быть перезагружен, а большой раздел безопасен, поскольку любой последующий запрос будет иметь новую статистику и, вероятно, будет использовать оптимальный план.

Инкрементальная статистика в любом случае включена, поэтому всякий раз, когда глобальность должна быть пересчитана, она довольно быстрая, поскольку агрегирует статистику уровня разделов и не выполняет полное сканирование.

Я не уверен, что с инкрементным включением "APPROX_GLOBAL AND PARTITION" и "GLOBAL AND PARTITION" чем-то отличаются друг от друга, потому что как инкрементные, так и приближенные делают в основном одно и то же: совокупная статистика и гистограммы без полного сканирования.

Ответ 2

Вы пытались включить инкрементную статистику, но все же явно указываете раздел для анализа?

 dbms_stats.gather_table_stats(ownname=>myschema,
                              tabname=>big_table,
                              partname=>part,
                              degree=>dbms_stats.auto_degree);

Ответ 3

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

  • У вас есть отдельный сбор GLOBAL stats, выполненный вашим инструментом ETL сразу после загрузки всех разделов. Если он занимает слишком много времени, играйте с оценкой_percent, поскольку dbms_stats.auto_degree, вероятно, будет больше 1%
  • Соберите глобальную (а также все другие устаревшие) статистику в отдельном задании базы данных, которое будет запущено позже в течение дня, после того как все данные будут загружены в DW.

Ключевым моментом является то, что устаревшая статистика, которая немного отличается от свежей, почти так же хороша. Если статистика показывает вам 0 строк, они будут убивать любой запрос.

Ответ 4

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

Однако для этого вам нужно включить инкрементную функцию для таблицы (11gR1).

EXEC DBMS_STATS.SET_TABLE_PREFS('<Owner>','BIG_TABLE','INCREMENTAL','TRUE');

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

EXEC DBMS_STATS.GATHER_TABLE_STATS('<Owner>','BIG_TABLE');

Ответ 5

Просьба проверить, используете ли вы DBMS_STATS для установки предпочтения таблицы для сбора инкрементной статистики. Этот блог оракула объясняет, что статистика будет собрана после каждой строки пострадавших.

Инкрементальное статистическое обслуживание должно собирать статистические данные о любом разделе, который изменит статистику глобального или табличного уровня. Например, минимальное или максимальное значение для столбца может измениться после того, как только одна строка будет вставлена ​​или обновлена ​​в таблице

BEGIN 
DBMS_STATS.SET_TABLE_PREFS(myschema,'BIG_TABLE','INCREMENTAL','TRUE'); 
END;

Ответ 6

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

Добавление подсказки влияет на размер повтора, то есть транзакция просто отслеживает что-то, поэтому статистика может не считаться с новыми данными: http://oracle-base.com/articles/misc/append-hint.php

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

Вы можете протестировать это поведение, временно переключив таблицу/раздел на LOGGING, например, и посмотрите, как это работает (медленнее, конечно, но это тест). Можете ли вы это сделать?

EDIT: поэтапная статистика должна работать в любом случае, даже отключая параллельный сбор статистики, поскольку она добавляет дополнительные значения независимо от того, как они были собраны: https://blogs.oracle.com/optimizer/entry/incremental_statistics_maintenance_what_statistics