14.4. Наполнение базы данных #
- 14.4.1. Отключите автофиксацию транзакций
- 14.4.2. Используйте
COPY
- 14.4.3. Удалите индексы
- 14.4.4. Удалите ограничения внешних ключей
- 14.4.5. Увеличьте
maintenance_work_mem
- 14.4.6. Увеличьте
max_wal_size
- 14.4.7. Отключите архивацию WAL и потоковую репликацию
- 14.4.8. Выполните в конце
ANALYZE
- 14.4.9. Несколько замечаний относительно pg_dump
- 14.4.2. Используйте
Довольно часто в начале или в процессе использования базы данных возникает необходимость загрузить в неё большой объём данных. В этом разделе приведены рекомендации, которые помогут сделать это максимально эффективно.
14.4.1. Отключите автофиксацию транзакций #
Выполняя серию команд INSERT
, выключите автофиксацию транзакций и зафиксируйте транзакцию только один раз в самом конце. (В обычном SQL это означает, что нужно выполнить BEGIN
до, и COMMIT
после этой серии. Некоторые клиентские библиотеки могут делать это автоматически, в таких случаях нужно убедиться, что это так.) Если вы будете фиксировать каждое добавление по отдельности, PostgreSQL придётся проделать много действий для каждой добавляемой строки. Выполнять все операции в одной транзакции хорошо ещё и потому, что в случае ошибки добавления одной из строк произойдёт откат к исходному состоянию и вы не окажетесь в сложной ситуации с частично загруженными данными.
14.4.2. Используйте COPY
#
Используйте COPY
, чтобы загрузить все строки одной командой вместо серии INSERT
. Команда COPY
оптимизирована для загрузки большого количества строк; хотя она не так гибка, как INSERT
, но при загрузке больших объёмов данных она влечёт гораздо меньше накладных расходов. Так как COPY
— это одна команда, применяя её, нет необходимости отключать автофиксацию транзакций.
В случаях, когда COPY
не подходит, может быть полезно создать подготовленный оператор INSERT
с помощью PREPARE
, а затем выполнять EXECUTE
столько раз, сколько потребуется. Это позволит избежать накладных расходов, связанных с разбором и анализом каждой команды INSERT
. В разных интерфейсах это может выглядеть по-разному; за подробностями обратитесь к описанию «подготовленных операторов» в документации конкретного интерфейса.
Заметьте, что с помощью COPY
большое количество строк практически всегда загружается быстрее, чем с помощью INSERT
, даже если используется PREPARE
и серия операций добавления заключена в одну транзакцию.
COPY
работает быстрее всего, если она выполняется в одной транзакции с командами CREATE TABLE
или TRUNCATE
. В таких случаях записывать WAL не нужно, так как в случае ошибки файлы, содержащие загружаемые данные, будут всё равно удалены. Однако это замечание справедливо, только когда параметр wal_level равен minimal
, так как в противном случае все команды должны записывать свои изменения в WAL.
14.4.3. Удалите индексы #
Если вы загружаете данные в только что созданную таблицу, быстрее всего будет загрузить данные с помощью COPY
, а затем создать все необходимые для неё индексы. На создание индекса для уже существующих данных уйдёт меньше времени, чем на последовательное его обновление при добавлении каждой строки.
Если вы добавляете данные в существующую таблицу, может иметь смысл удалить индексы, загрузить таблицу, а затем пересоздать индексы. Конечно, при этом надо учитывать, что временное отсутствие индексов может отрицательно повлиять на скорость работы других пользователей. Кроме того, следует дважды подумать, прежде чем удалять уникальные индексы, так как без них соответствующие проверки ключей не будут выполняться.
14.4.4. Удалите ограничения внешних ключей #
Как и с индексами, проверки, связанные с ограничениями внешних ключей, выгоднее выполнять «массово», а не для каждой строки в отдельности. Поэтому может быть полезно удалить ограничения внешних ключей, загрузить данные, а затем восстановить прежние ограничения. И в этом случае тоже приходится выбирать между скоростью загрузки данных и риском допустить ошибки в отсутствие ограничений.
Более того, когда вы загружаете данные в таблицу с существующими ограничениями внешнего ключа, для каждой новой строки добавляется запись в очередь событий триггера (так как именно срабатывающий триггер проверяет такие ограничения для строки). При загрузке многих миллионов строк очередь событий триггера может занять всю доступную память, что приведёт к недопустимой нагрузке на файл подкачки или даже к сбою команды. Таким образом, загружая большие объёмы данных, может быть не просто желательно, а необходимо удалять, а затем восстанавливать внешние ключи. Если же временное отключение этого ограничения неприемлемо, единственно возможным решением может быть разделение всей операции загрузки на меньшие транзакции.
14.4.5. Увеличьте maintenance_work_mem
#
Ускорить загрузку больших объёмов данных можно, увеличив параметр конфигурации maintenance_work_mem на время загрузки. Это приведёт к увеличению быстродействия CREATE INDEX
и ALTER TABLE ADD FOREIGN KEY
. На скорость самой команды COPY
это не повлияет, так что этот совет будет полезен, только если вы применяете какой-либо из двух вышеописанных приёмов.
14.4.6. Увеличьте max_wal_size
#
Также массовую загрузку данных можно ускорить, изменив на время загрузки параметр конфигурации max_wal_size. Загружая большие объёмы данных, PostgreSQL вынужден увеличивать частоту контрольных точек по сравнению с обычной (которая задаётся параметром checkpoint_timeout
), а значит и чаще сбрасывать «грязные» страницы на диск. Временно увеличив max_wal_size
, можно уменьшить частоту контрольных точек и связанных с ними операций ввода-вывода.
14.4.7. Отключите архивацию WAL и потоковую репликацию #
Для загрузки больших объёмов данных в среде, где используется архивация WAL или потоковая репликация, быстрее будет сделать копию базы данных после загрузки данных, чем обрабатывать множество операций изменений в WAL. Чтобы отключить передачу изменений через WAL в процессе загрузки, отключите архивацию и потоковую репликацию, назначьте параметру wal_level значение minimal
, archive_mode — off
, а max_wal_senders — 0. Но имейте в виду, что изменённые параметры вступят в силу только после перезапуска сервера, а все ранее созданные резервные копии не будут пригодны для резервного сервера и восстановления архива, что может привести к потере данных.
Это не только поможет сэкономить время архивации и передачи WAL, но и непосредственно ускорит некоторые команды, потому что они не записывают в WAL ничего, если в wal_level
установлен уровень minimal
и текущая подтранзакция (или транзакция верхнего уровня) создала и опустошила таблицу или индекс, куда затем вносятся изменения. (Они могут гарантировать безопасность данных при сбое, не записывая их в WAL, а только выполнив fsync
в конце, что будет гораздо дешевле.)
14.4.8. Выполните в конце ANALYZE
#
Всякий раз, когда распределение данных в таблице значительно меняется, настоятельно рекомендуется выполнять ANALYZE
. Эта рекомендация касается и загрузки в таблицу большого объёма данных. Выполнив ANALYZE
(или VACUUM ANALYZE
), вы тем самым обновите статистику по данной таблице для планировщика. Когда планировщик не имеет статистики или она не соответствует действительности, он не сможет правильно планировать запросы, что приведёт к снижению быстродействия при работе с соответствующими таблицами. Заметьте, что если включён демон автоочистки, он может запускать ANALYZE
автоматически; подробнее об этом можно узнать в Подразделе 24.1.3 и Подразделе 24.1.6.
14.4.9. Несколько замечаний относительно pg_dump #
В скриптах загрузки данных, которые генерирует pg_dump, автоматически учитываются некоторые, но не все из этих рекомендаций. Чтобы загрузить данные, которые выгрузил pg_dump, максимально быстро, вам нужно будет выполнить некоторые дополнительные действия вручную. (Заметьте, что эти замечания относятся только к восстановлению данных, но не к выгрузке их. Следующие рекомендации применимы вне зависимости от того, загружается ли архивный файл pg_dump в psql или в pg_restore.)
По умолчанию pg_dump использует команду COPY
и когда она выгружает полностью схему и данные, в сгенерированном скрипте она сначала предусмотрительно загружает данные, а потом создаёт индексы и внешние ключи. Так что в этом случае часть рекомендаций выполняется автоматически. Вам остаётся учесть только следующие:
Установите подходящие (то есть превышающие обычные) значения для
maintenance_work_mem
иmax_wal_size
.Если вы используете архивацию WAL или потоковую репликацию, по возможности отключите их на время восстановления. Для этого перед загрузкой данных, присвойте параметру
archive_mode
значениеoff
,wal_level
—minimal
, аmax_wal_senders
— 0. Закончив восстановление, верните их обычные значения и сделайте свежую базовую резервную копию.Поэкспериментируйте с режимами параллельного копирования и восстановления команд pg_dump и pg_restore, и подберите оптимальное число параллельных заданий. Параллельное копирование и восстановление данных, управляемое параметром
-j
, должно дать значительный выигрыш в скорости по сравнению с последовательным режимом.Если это возможно в вашей ситуации, восстановите все данные в рамках одной транзакции. Для этого передайте параметр
-1
или--single-transaction
команде psql или pg_restore. Но учтите, что в этом режиме даже незначительная ошибка приведёт к откату всех изменений и часы восстановления будут потрачены зря. В зависимости от того, насколько взаимосвязаны данные, предпочтительнее может быть вычистить их вручную. КомандыCOPY
будут работать максимально быстро, когда они выполняются в одной транзакции и архивация WAL выключена.Если на сервере баз данных установлено несколько процессоров, полезным может оказаться параметр
--jobs
команды pg_restore. С его помощью можно выполнить загрузку данных и создание индексов параллельно.После загрузки данных запустите
ANALYZE
.
При выгрузке данных без схемы тоже используется команда COPY
, но индексы, как обычно и внешние ключи, при этом не удаляются и не пересоздаются. [14] Поэтому, загружая только данные, вы сами должны решить, нужно ли для ускорения загрузки удалять и пересоздавать индексы и внешние ключи. При этом будет так же полезно увеличить параметр max_wal_size
, но не maintenance_work_mem
; его стоит менять, только если вы впоследствии пересоздаёте индексы и внешние ключи вручную. И не забудьте выполнить ANALYZE
после; подробнее об этом можно узнать в Подразделе 24.1.3 и Подразделе 24.1.6.
[14] Вы можете отключить внешние ключи, используя параметр --disable-triggers
— но при этом нужно понимать, что тем самым вы не просто отложите, а полностью выключите соответствующие проверки, что позволит вставить недопустимые данные.