Найповільніший спосіб - окремий INSERT на кожен рядок в autocommit: кожен рядок - окрема транзакція з записом у WAL і синхронізацією з диском.
Від швидкого до найшвидшого:
1. Одна транзакція і масові INSERT:
BEGIN;
INSERT INTO events (user_id, type, created_at) VALUES
(1, 'login', now()), (2, 'login', now()), /* ...тисяча рядків... */;
-- ще порції
COMMIT;
Порції по 500-5000 рядків в одному INSERT - у десятки разів швидше за поодинокі. У Laravel - DB::table('events')->insert($batch).
2. COPY - спеціальний протокол масового завантаження, найшвидший спосіб:
COPY events (user_id, type, created_at) FROM '/data/events.csv' WITH (FORMAT csv, HEADER true);
COPY ... FROM читає файл на сервері бази; з клієнта - \copy у psql чи Pdo\Pgsql::copyFromFile() / copyFromArray() у PHP 8.4+ (раніше - методи pgsqlCopyFrom* на самому PDO).
3. Для величезних одноразових завантажень:
- Створити індекси й зовнішні ключі після завантаження - побудувати індекс один раз швидше, ніж оновлювати його мільйон разів.
- Збільшити
maintenance_work_memдля швидшої побудови індексів. UNLOGGED-таблиця для проміжних даних - без запису в WAL, але не переживає збій і не реплікується.- Тимчасово збільшити
max_wal_size, щоб рідше робилися контрольні точки.
Після завантаження - обов'язково ANALYZE, інакше планувальник працюватиме зі статистикою порожньої таблиці. А для таблиць, куди більше не писатимуть, - VACUUM (FREEZE, ANALYZE), щоб пізніше не було масового «заморожування».
Обережно на проді: масове завантаження генерує багато WAL - репліки відстають, а сервер з архівуванням WAL заповнює сховище. Великі імпорти ділять на порції з паузами й стежать за затримкою реплікації.