Оптимизация производительности массовых обновлений в PostgreSQL

37

Использование PG 9.1 в Ubuntu 12.04.

В настоящее время нам требуется до 24 часов для выполнения большого набора операторов UPDATE в базе данных, которые имеют вид:

UPDATE table
SET field1 = constant1, field2 = constant2, ...
WHERE id = constid

(Мы просто перезаписываем поля объектов, идентифицированных по ID.) Значения поступают из внешнего источника данных (еще нет в БД в таблице).

Таблицы имеют несколько индексов и не имеют ограничений по внешнему ключу. Коммит не делается до конца.

Для импорта pg_dumpвсей БД требуется 2 часа . Это похоже на базовый уровень, на который мы должны разумно ориентироваться.

Если не считать создание специальной программы, которая каким-то образом восстанавливает набор данных для PostgreSQL для повторного импорта, есть ли что-то, что мы можем сделать, чтобы приблизить объемную производительность UPDATE к производительности импорта? (Это та область, в которой, по нашему мнению, деревья слияния с лог-структурой хорошо обрабатываются, но нам интересно, можно ли что-нибудь сделать в PostgreSQL.)

Некоторые идеи:

  • сбросить все неидентификационные индексы и восстановить после этого?
  • увеличение контрольных точек, но помогает ли это в долгосрочной перспективе?
  • используя методы, упомянутые здесь ? (Загрузить новые данные в виде таблицы, затем «объединить» старые данные, где идентификатор не найден в новых данных)

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

У меня есть параллельная загрузка таблицы, но она доступна только для чтения.

Ян
источник
В вашем вопросе отсутствует важная информация: ваша версия Postgres? Откуда берутся ценности? Звучит как файл вне базы данных, но, пожалуйста, уточните. Есть ли у вас одновременная загрузка на целевой таблице? Если да, то что именно? Или вы можете позволить себе бросить и воссоздать? Нет внешних ключей, хорошо - но есть ли другие зависимые объекты, такие как представления? Пожалуйста, отредактируйте ваш вопрос с отсутствующей информацией. Не сжимайте это в комментарии.
Эрвин Брандштеттер,
@ErwinBrandstetter Спасибо, обновил мой вопрос.
Ян
Я полагаю, вы проверили, explain analyzeчто он использует индекс для поиска?
rogerdpack

Ответы:

45

Предположения

Поскольку информация в Q отсутствует, я буду считать:

  • Ваши данные поступают из файла на сервере базы данных.
  • Данные форматируются так же, как COPYвыходные данные, с уникальным значением для id каждой строки, чтобы соответствовать целевой таблице.
    Если нет, сначала отформатируйте его правильно или используйте COPYпараметры для работы с форматом.
  • Вы обновляете каждую строку в целевой таблице или большинство из них.
  • Вы можете позволить себе сбросить и воссоздать целевой стол.
    Это означает отсутствие одновременного доступа. Еще рассмотрим этот связанный ответ:
  • Зависимых объектов нет вообще, кроме индексов.

Решение

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

Для создания временной таблицы есть более простой и быстрый способ:

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

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

В модели MVCC PostgreSQL - UPDATEсредство для создания новой версии строки и пометки старой как удаленной. Это примерно так же дорого, как INSERTи в DELETEсочетании. Плюс, это оставляет вас с множеством мертвых кортежей. Так как вы в любом случае обновляете всю таблицу, в целом быстрее будет просто создать новую таблицу и удалить старую.

Если у вас достаточно ОЗУ, установите temp_buffers(только для этого сеанса!) Достаточно высоко, чтобы держать временную таблицу в ОЗУ, прежде чем делать что-либо еще.

Чтобы оценить, сколько ОЗУ необходимо, запустите тест с небольшой выборкой и используйте функции размера объекта БД :

SELECT pg_size_pretty(pg_relation_size('tmp_tbl'));  -- complete size of table
SELECT pg_column_size(t) FROM tmp_tbl t LIMIT 10;  -- size of sample rows

Полный сценарий

SET temp_buffers = '1GB';        -- example value

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

COPY tmp_tbl FROM '/absolute/path/to/file';

CREATE TABLE tbl_new AS
SELECT t.col1, t.col2, u.field1, u.field2
FROM   tbl     t
JOIN   tmp_tbl u USING (id);

-- Create indexes like in original table
ALTER TABLE tbl_new ADD PRIMARY KEY ...;
CREATE INDEX ... ON tbl_new (...);
CREATE INDEX ... ON tbl_new (...);

-- exclusive lock on tbl for a very brief time window!
DROP TABLE tbl;
ALTER TABLE tbl_new RENAME TO tbl;

DROP TABLE tmp_tbl; -- will also be dropped at end of session automatically

Параллельная загрузка

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

ОБНОВЛЕНИЕ маршрута

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

Я изложил аналогичную процедуру, используя UPDATEв этом тесно связанном ответе на SO .

 

Эрвин Брандштеттер
источник
1
Я на самом деле просто обновляю 20% строк в целевой таблице - не все, но достаточно большую часть, чтобы объединение, вероятно, лучше, чем ищет случайное обновление.
Ян
1
@AryehLeibTaurog: Этого не должно быть, так как DROP TABLEвынимает Access Exclusive Lock. В любом случае, я уже перечислил обязательное условие в верхней части моего ответа: You can afford to drop and recreate the target table.это может помочь заблокировать таблицу в начале транзакции. Я предлагаю вам начать новый вопрос со всеми соответствующими деталями вашей ситуации, чтобы мы могли разобраться в этом.
Эрвин Брандштеттер
1
@ErwinBrandstetter Интересно. Кажется, это зависит от версии сервера. Я воспроизвел ошибку на 8.4 и 9.1 с помощью адаптера psycopg2 и клиента psql . На 9.3 ошибки нет. Смотрите мои комментарии в первом скрипте. Я не уверен, есть ли вопрос для публикации здесь, но, возможно, стоит запросить некоторую информацию в одном из списков postgresql.
Арье Лейб Таурог
1
Я написал простой вспомогательный класс в Python для автоматизации процесса.
Арье Лейб Таурог
3
Очень полезный ответ. В качестве небольшого изменения можно создать временную таблицу только с обновляемыми столбцами и ссылочными столбцами, удалить столбцы, подлежащие обновлению, из исходной таблицы, а затем объединить таблицы, используя CREATE TABLE tbl_new AS SELECT t.*, u.field1, u.field2 from tbl t NATURAL LEFT JOIN tmp_tbl u;, LEFT JOINпозволяя сохранять строки, для которых нет обновления. Конечно, NATURALможно изменить на любой действительный USING()или ON.
Скиппи ле Гран Гуру
2

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

Дэвид Олдридж
источник
3
Что вы конкретно имеете в виду под «слиянием на целевой таблице»? Почему использование FDW лучше, чем КОПИРОВАНИЕ во временную таблицу (как предложено в третьем пункте в исходном вопросе)?
Ян
«Объединить», как в MERGE SQL заявление. Использование FDW позволяет сделать это без дополнительного шага копирования данных во временную таблицу. Я предполагаю, что вы не заменяете весь набор данных, и что в файле будет определенный объем данных, который не будет представлять собой изменение по сравнению с текущим набором данных - если значительное количество изменилось, то полное замена таблицы может быть целесообразной.
Дэвид Олдридж
1
@DavidAldridge: Хотя он определен в стандарте SQL: 2003, он MERGEеще не реализован в PostgreSQL (пока). Реализации в других СУБД сильно различаются. Рассмотрим информацию тега для MERGEи UPSERT.
Эрвин Брандштеттер
@ErwinBrandstetter [глурк] Да, конечно. Ну, я думаю, Merge - это глазурь на торте. Доступ к данным без шага импорта во временную таблицу - это настоящая суть метода FDW.
Дэвид Олдридж