Улучшение времени DELETE и INSERT в большой таблице со структурой индекса

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

Структура этой таблицы в основном следующая:

CREATE TABLE per_user_result_set
           ( user_login         VARCHAR2(N)
           , result_set_item_id VARCHAR2(M)
           , CONSTRAINT result_set_pk PRIMARY KEY(user_login, result_set_item_id)
           )
           ;

Типичный пользователь нашего приложения будет иметь этот набор результатов, вычисляемый 30 раз в день, с набором результатов, состоящим из одного элемента до 500 000 элементов. Типичный клиент объявит около 500 пользователей в рабочей базе данных. Итак, эта таблица обычно состоит из 5 миллионов строк.

Типичный запрос, который мы используем для обновления этой таблицы:

BEGIN
    DELETE FROM per_user_result_set WHERE user_login = :x;
    INSERT INTO per_user_result_set(...) SELECT :x, ... FROM ...;
END;
/

После того, как мы столкнулись с проблемами производительности (часть DELETE заняла бы много времени), мы решили иметь ГЛОБАЛЬНУЮ ВРЕМЕННУЮ ТАБЛИЦУ (при фиксации удаляемых строк) для хранения «дельты» строк, которые нужно исключить из таблицы, и строк, которые нужно вставить в нее:

BEGIN
    INSERT INTO _tmp
    SELECT ... FROM ...
     MINUS SELECT result_set_item_id
             FROM per_user_result_set
            WHERE user_login = :x;

    DELETE FROM per_user_result_set
          WHERE user_login = :x
            AND result_set_item_id NOT IN (SELECT result_set_item_id
                                             FROM _tmp
                                          );
    INSERT INTO per_user_result_set
    SELECT :x, result_set_item_id
      FROM _tmp;

    COMMIT;
END;
/

Это немного улучшило производительность, но все равно это неудовлетворительно. Итак, мы изучаем способы ускорить этот процесс, и вот проблемы, с которыми мы сталкиваемся:

  • Нам бы очень хотелось использовать секционирование таблицы (разбиение по user_login). Но разбиение не всегда доступно (в наших тестовых базах данных мы получили ORA-00439). Не все наши клиенты могут позволить себе Oracle Enterprise Edition с платными дополнительными функциями.
  • Мы могли бы сделать таблицу per_user_result_set ГЛОБАЛЬНОЙ ВРЕМЕННОЙ, чтобы она была изолирована, и мы могли бы, например, TRUNCATE... но наше приложение иногда теряет соединение с Oracle из-за проблем с сетью и автоматически подключается снова. К тому времени мы теряем содержимое наших вычислений.
  • Мы могли бы разбить эту таблицу на определенное количество сегментов, создать представление, которое объединяет ВСЕ эти сегменты и запускает INSTEAD OF UPDATE и DELETE в этом представлении, а также разделять строки в соответствии с ORA_HASH(user_login) % num_buckets. Но мы опасаемся, что это может сильно замедлить SELECT операции. Это приведет к постоянному количеству таблиц с меньшими индексами, затронутыми операциями DELETE или INSERT. Одним словом, «разделочный стол для бедных».
  • Мы пытались ALTER TABLE per_user_result_set NOLOGGING. Это не сильно улучшает ситуацию.
  • Мы пытались CREATE TABLE ... ORGANIZATION INDEX COMPRESS 1. Это ускоряет работу в соотношении 1:5.
  • Мы пытались создать одну таблицу для каждого user_login. Это именно то, что мы могли бы получить, используя количество разделов, равное количеству различных user_logins, и хорошо подобранную хеш-функцию. Коэффициент производительности 1:10. Но мне бы очень хотелось избежать этого решения: приходится поддерживать огромное количество индексов, таблиц, представлений для каждого пользователя. Это было бы интересным приростом производительности для пользователей, но не для нас, сопровождающих системы.
  • Поскольку пользователи работают одновременно, мы не можем создать новую таблицу и поменять ее местами со старой.

Что бы вы могли предложить в дополнение к этим подходам?

Примечание. Наши клиенты используют базы данных Oracle от 9i до 11g и версии XE до версии Enterprise. Это широкий спектр версий, с которыми мы должны быть совместимы.

Спасибо.


person Benoit    schedule 27.02.2012    source источник
comment
Моей первой мыслью было сделать таблицу IOT, но вы сделали это, и это привело к 5-кратному ускорению, верно? Единственное, о чем я могу думать, это избежать удаления. Каким-то образом используйте последовательность для устаревания старого набора результатов каждый раз, когда выполняется новый запрос, т. е. таблица становится user_login, result_set_id, result_set_item_id. Возможно, вам потребуется отслеживать последний результат_set_id в таблице пользователей или что-то в этом роде. Вы на 100% уверены, что вставка/удаление является проблемой, а медленная часть не находит result_set_item_id?   -  person Stephen ODonnell    schedule 27.02.2012
comment
@Stephen ODonnell: я уверен, что большую часть времени приходится на часть DELETE.   -  person Benoit    schedule 27.02.2012
comment
Что вы уже сделали для сбора подробных данных трассировки? Мы можем догадываться о решениях сколько угодно, но вам нужны данные, чтобы действительно понять проблему и решить ее. Книга Джонатана Льюиса Cost-Based Oracle Fundamentals могла бы стать отличной отправной точкой.   -  person Jim Hudson    schedule 27.02.2012
comment
Длинный план, но используете ли вы табличные пространства ASSM? Я видел серьезные проблемы с удалением и вставкой больших объемов данных — например, ошибка 4475314, но «исправление» для нее, похоже, не решило ее полностью — что мы могли решить, только переместив затронутые таблицы в MSSM.   -  person Alex Poole    schedule 28.02.2012


Ответы (2)


Мы пытались создать одну таблицу для каждого user_login. Это именно то, что мы могли бы получить, используя количество разделов, равное количеству различных user_logins, и хорошо подобранную хеш-функцию. Коэффициент производительности 1:10. Но мне бы очень хотелось избежать этого решения: приходится поддерживать огромное количество индексов, таблиц, представлений для каждого пользователя. Это было бы интересным приростом производительности для пользователей, но не для нас, сопровождающих системы.

Можете ли вы затем создать хранимую процедуру для создания этих таблиц для каждого пользователя? Или, что еще лучше, эта хранимая процедура делает наиболее подходящие действия в зависимости от поддерживаемой лицензии Oracle?

If Partitioning option 
  then create or truncate user-specific list partition
Else 
  drop user-specific result table
  Create user-specific result table 
      as Select from template result table
  create indexes
  create constraints
  perform grants
end if
Perform insert
person Adam Musch    schedule 27.02.2012

Если бы все ваши пользователи использовали 11g Enterprise Edition, я бы рекомендовал вам использовать Встроенное кэширование набора результатов Oracle вместо того, чтобы пытаться создать собственное. Но это не так, так что идем дальше.

Другим привлекательным вариантом может быть использование коллекций PL/SQL, а не таблиц. Находясь в памяти, они быстрее извлекаются и требуют меньшего обслуживания. Они также поддерживаются во всех необходимых вам версиях. Однако они являются переменными сеанса, поэтому, если у вас много пользователей с большими наборами результатов, это может увеличить нагрузку на ваши выделения PGA. Также их данные будут потеряны при обрыве сетевого соединения. Так что это, вероятно, не то решение, которое вы ищете.

Суть вашей проблемы в следующем утверждении:

DELETE FROM per_user_result_set WHERE user_login = :x;

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

Вы говорите, что не хотите иметь таблицу для каждого пользователя, потому что

«[это] было бы интересным приростом производительности для пользователей, но не для нас, специалистов по обслуживанию систем»,

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

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

Я предлагаю вам придерживаться индексно-организованных таблиц. Вам нужны только столбцы, которые находятся в первичном ключе, и поддержка отдельного индекса не требует дополнительных затрат (как для вставки, так и для удаления). Большим преимуществом наличия таблицы для каждого пользователя является то, что вы можете использовать TRUNCATE TABLE в процессе обновления, что намного быстрее, чем удаление.

Таким образом, ваша процедура обновления будет выглядеть так:

BEGIN
    TRUNCATE TABLE per_user_result_set REUSE STORAGE;
    INSERT INTO per_user_result_set(...) 
          SELECT ...  FROM  ...;
    DBMS_STATS.GATHER_TABLE_STATS(user
          , 'PER_USER_RESULT_SET'
          , estimate_percent=>10);
    COMMIT;
END;
/

Обратите внимание, что вам больше не нужно включать столбец USER, поэтому в вашей таблице будет только один столбец result_set_item_id (еще одно указание на пригодность IOT.

Сбор статистики за столом не обязателен, но желателен. У вас есть широкий диапазон размеров наборов результатов, и вы не хотите использовать план выполнения, разработанный для 500 000 строк, когда в таблице есть только одна строка, или наоборот.

Единственными накладными расходами является необходимость создания таблицы в пользовательской схеме. Но, по-видимому, у вас уже есть некоторые настройки для нового пользователя — создание учетной записи, предоставление привилегий и т. д. — так что это не должно быть большой проблемой.

person APC    schedule 01.03.2012