Привет, Хабр!

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

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

В этой статье рассмотрим, как реализовать пакетную обработку в PostgreSQL.

Способы пакетной обработки данных

Команда COPY - самый хороший способ массового ввода и вывода данных. Она позволяет быстро загружать данные из файла в табличку БД или экспортировать данные из таблицы в файл. COPY использует высокопроизводительный протокол для перемещения данных между файлом и таблицей:

COPY my_table (column1, column2, column3)
FROM '/path/to/file.csv'
WITH (FORMAT csv, HEADER true, DELIMITER ',');

Тут данные из файла file.csv загружаются в таблицу my_table. Опции FORMAT csv, HEADER true и DELIMITER ',' указывают на формат файла, наличие заголовков и разделитель столбцов соответственно.

COPY поддерживает различные форматы файлов, а также может использоваться в транзакциях.

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

Пример:

BEGIN;

-- создание временной таблицы
CREATE TEMP TABLE temp_table AS
SELECT id, new_value
FROM source_table;

-- обновление основной таблицы на основе временной
UPDATE main_table
SET column_name = temp_table.new_value
FROM temp_table
WHERE main_table.id = temp_table.id;

-- удаление записей на основе временной таблицы
DELETE FROM main_table
USING temp_table
WHERE main_table.id = temp_table.id;

COMMIT;

Временная таблица temp_table используется для обновления и удаления записей в основной таблице main_table.

Для вставки большого количества записей можно использовать многоразовые вставки, которые выполняются в одном запросе, например:

INSERT INTO my_table (column1, column2, column3)
VALUES 
  (value1_1, value1_2, value1_3),
  (value2_1, value2_2, value2_3),
  (value3_1, value3_2, value3_3);

Для операций посложней можно использовать процедуры на PL/pgSQL:

DO $$
BEGIN
  FOR i IN 1..1000 LOOP
    INSERT INTO my_table (column1, column2)
    VALUES (i, i * 2);
  END LOOP;
END $$;

Так можно вставить 1000 записей в таблицу my_table в рамках одной процедуры.

Преимущества использования многоразовых вставок и PL/pgSQL:

  • Сокращение накладных расходов на обработку транзакций.

  • Повышение производительности за счет пакетной обработки данных.

Как все это дело оптимизировать

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

Отключаем индексы:

DROP INDEX index_name1, index_name2, ...;

Выполняем массовую операцию, например вставку данных.

Восстанавливаем индексы:

CREATE INDEX index_name1 ON table_name(column1);
CREATE INDEX index_name2 ON table_name(column2);

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

Отключаем триггеры:

ALTER TABLE table_name DISABLE TRIGGER ALL;

После выполнения массовых операций включаем с помощью ENABLE.

Плюсом можно подключить нелогируемые таблицы и поработать с настройкой work_mem.

Примеры

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

-- отключение индексов перед вставкой данных
DROP INDEX IF EXISTS idx_column1, idx_column2;

-- массовая вставка данных с использованием команды COPY
COPY target_table (column1, column2)
FROM '/path/to/your/file.csv'
WITH (FORMAT csv, HEADER true);

-- восстановление индексов после вставки данных
CREATE INDEX idx_column1 ON target_table (column1);
CREATE INDEX idx_column2 ON target_table (column2);

Пакетное обновление данных с использованием временной таблички:

BEGIN;

-- создание временной таблицы
CREATE TEMP TABLE temp_updates AS
SELECT id, new_value FROM source_table;

-- обновление основной таблицы на основе данных из временной таблицы
UPDATE target_table
SET column_to_update = temp_updates.new_value
FROM temp_updates
WHERE target_table.id = temp_updates.id;

COMMIT;

Пакетное удаление данных с PL/pgSQL:

BEGIN;

-- отключение всех триггеров на целевой таблице
ALTER TABLE target_table DISABLE TRIGGER ALL;

-- пакетное удаление данных с использованием PL/pgSQL
DO $$
DECLARE
    r RECORD;
BEGIN
    FOR r IN (SELECT id FROM source_table WHERE condition) LOOP
        DELETE FROM target_table WHERE id = r.id;
    END LOOP;
END $$;

-- включение всех триггеров на целевой таблице
ALTER TABLE target_table ENABLE TRIGGER ALL;

COMMIT;

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

Комментарии (18)


  1. odilovoybek
    25.06.2024 18:25

    Большое спасибо, на днях чудил с криптоагрегатором и с API подгружал кучу данных, пригодится. Кажется что база, но тут есть некоторые тонкости для меня. А может кто-то порекомендовать хороший курс или книжку по Postgre?


    1. miruzzy
      25.06.2024 18:25
      +5

      https://www.postgrespro.ru/education/courses
      Ну как минимум вот эти курсы довольно хороши

      Из книг - ну если нужно что-то на русском языке - опять же от этих ребят есть 2 книги по постгресу. Одна у Рогова, вторая у Моргунова


  1. 0pauc0
    25.06.2024 18:25
    +9

    Пересоздать индекс, отключить триггеры? Вы серьезно? На рабочей базе?


    1. lampa
      25.06.2024 18:25

      Если БД используется как olap, то всё это рабочие схемы. Переливки во временные таблицы, отключение репликации, расчёт на партициях, блин да даже ram диск использовать это вполне норм. Да, тогда надо брать спарк, но тут речь про постгрю :-)


      1. 0pauc0
        25.06.2024 18:25

        Если для статики, то наверное и возможно, в том числе и на PG, если динамическая часть тоже на нём. Но откуда в статике триггеры?


  1. Batalmv
    25.06.2024 18:25
    +9

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

    Ну а пакетная обработка одним update даже на уровень песочницы не тянет


  1. Koyotter
    25.06.2024 18:25
    +4

    Вот это

    FOR r IN (SELECT id FROM source_table WHERE condition) LOOP DELETE FROM target_table WHERE id = r.id; END LOOP;

    будет работать на порядок дольше, чем

    DELETE FROM target_table WHERE id in (SELECT id FROM source_table WHERE condition)


    1. lampa
      25.06.2024 18:25

      Вообще если предполагается какое-то массовое удаление и речь зашла про доп. подготовки вроде отключения триггеров, то есть еще вариант - таблицу готовим в рид онли, на её основе создаем новую через `create as select` и переименовываем новую в старую, а старую потом дропаем, иначе это удаление вообще может зависнуть)))


  1. Koyotter
    25.06.2024 18:25

    Вот это

    FOR r IN (SELECT id FROM source_table WHERE condition) LOOP DELETE FROM target_table WHERE id = r.id; END LOOP;

    будет работать на порядок дольше, чем

    DELETE FROM target_table WHERE id in (SELECT id FROM source_table WHERE condition)


  1. Koyotter
    25.06.2024 18:25

    Вот это

    FOR r IN (SELECT id FROM source_table WHERE condition) LOOP DELETE FROM target_table WHERE id = r.id; END LOOP;

    будет работать на порядок дольше, чем

    DELETE FROM target_table WHERE id in (SELECT id FROM source_table WHERE condition)


  1. Koyotter
    25.06.2024 18:25

    Вот это

    FOR r IN (SELECT id FROM source_table WHERE condition) LOOP DELETE FROM target_table WHERE id = r.id; END LOOP;

    будет работать на порядок дольше, чем

    DELETE FROM target_table WHERE id in (SELECT id FROM source_table WHERE condition)


  1. Akina
    25.06.2024 18:25
    +2

    Так можно вставить 1000 записей в таблицу my_table в рамках одной процедуры.

    Так можно только по рукам получить. Ну или по голове. generate_series() для кого придуманы?

    Пакетное удаление данных с PL/pgSQL

    И за это тоже бить будут. DELETE имеет опциональный USING clause. Именно для таких целей.


  1. Koyotter
    25.06.2024 18:25
    +2

    Извините за дубликаты комментариев, ошибку выдавал, а оказалось что запостило


  1. miruzzy
    25.06.2024 18:25
    +3

    Плюсом можно подключить нелогируемые таблицы и поработать с настройкой work_mem.

    Что, зачем это делать, как это поможет и при каких условиях ?

    DO $$
    BEGIN
      FOR i IN 1..1000 LOOP
        INSERT INTO my_table (column1, column2)
        VALUES (i, i * 2);
      END LOOP;
    END $$;

    А это что такое, почему не generate_seriaes ? ( тот же вопрос и про удаление ( коммент был выше, да и вообще, почему не транкейт ? )

    Раз уж массовая производительная обработка, то почему нет `merge` ?

    Просто вброс какой-то инфы, взятой из разных источников, вообще не информативно ничего.


  1. breninsul
    25.06.2024 18:25
    +1

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

    Круто, конечно, что у нас бизнес-логика сломается, но не очень


  1. vmalyutin
    25.06.2024 18:25
    +1

    Автор про партиции что-нибудь слышал?

    Автор про мусор и вакумм что-нибудь слышал?

    Можно, конечно, над таблицей и так плясать и эдак, но почему бы не ограничить доступ к таблице, если уж она никому не нужна, без индексов и тригеров-то? Я бы уж тогда и fk прибил бы. Можно ж восстановить.

    Есть и другие способы избавиться/обновить записи. Но надо сначала те что выше отработать.


  1. CHIM86
    25.06.2024 18:25

    А есть ли вариант напрямую из xml файла раскладывать содержимое тегов или атрибутов по порядку в колонки таблицы?


    1. Akina
      25.06.2024 18:25

      Конечно, можно. XML - это же текст. Текстовых функций в постгрессе - вагон и маленькая тележка, распарсить можно что угодно и как угодно.

      Хотя это дурь, конечно. Ибо в Постгрессе есть и горсть XML-функций. В том числе XMLTABLE().