Массовое / пакетное обновление / вставка в PostgreSQL - PullRequest
43 голосов
/ 11 августа 2011

Я пишу расширение Django-ORM, которое пытается кэшировать модели и откладывает сохранение моделей до конца транзакции.Это почти все сделано, однако я столкнулся с неожиданной трудностью в синтаксисе SQL.

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

Теперь, насколько я вижу, SQL на самом деле не предоставляет никаких операторов для выполнения пакетного обновления.на столе.Термин, кажется, сбивает с толку , поэтому я объясню, что я имею в виду под этим.У меня есть массив произвольных данных, каждая запись описывает одну строку в таблице.Я хотел бы обновить определенные строки в таблице, каждая из которых использует данные из соответствующей записи в массиве.Идея очень похожа на пакетную вставку.

Например: моя таблица может иметь два столбца "id" и "some_col".Теперь массив, описывающий данные для пакетного обновления, состоит из трех записей (1, 'first updated'), (2, 'second updated') и (3, 'third updated').Перед обновлением таблица содержит строки: (1, 'first'), (2, 'second'), (3, 'third').

Я наткнулся на это сообщение:

Почему пакетные вставки / обновления быстрее?Как работают пакетные обновления?

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

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

Я работаю с PostgreSQL 8.4, поэтому здесь также возможны некоторые хранимые процедуры.Однако, поскольку я планирую в конечном итоге открыть исходный код проекта, любые более переносимые идеи или способы сделать то же самое в другой СУБД приветствуются.

Последующий вопрос: Как сделатьпакетный оператор «вставка или обновление» / «upsert»?

результаты теста

Я выполнил 100 раз 10 операций вставки, распределенных по 4 различным таблицам (так1000 вкладок в общей сложности).Я тестировал на Django 1.3 с бэкэндом PostgreSQL 8.4.

Вот результаты:

  • Все операции, выполняемые через Django ORM - каждый проход ~ 2,45 секунды ,
  • Те же операции, но без Django ORM - каждый проход ~ 1,48 секунды ,
  • Только операции вставки, без запроса базы данных для значений последовательности ~ 0,72секунд ,
  • Только операции вставки, выполненные в блоках по 10 (всего 100 блоков) ~ 0,19 секунд ,
  • Только операции вставки, один большой блок выполнения ~ 0,13 секунды .
  • Только операции вставки, около 250 операторов на блок, ~ 0,12 секунды .

Вывод: выполнить столько, сколькооперации, насколько это возможно в одном подключении. execute ().Сам Django вносит существенные издержки.

Отказ от ответственности: я не представил никаких индексов, кроме индексов первичного ключа по умолчанию, поэтому операции вставки могут выполняться быстрее из-за этого.

Ответы [ 5 ]

51 голосов
/ 26 ноября 2013

Массовая вставка

Вы можете изменить массовую вставку трех столбцов с помощью Ketema:

INSERT INTO "table" (col1, col2, col3)
  VALUES (11, 12, 13) , (21, 22, 23) , (31, 32, 33);

Это становится:

INSERT INTO "table" (col1, col2, col3)
  VALUES (unnest(array[11,21,31]), 
          unnest(array[12,22,32]), 
          unnest(array[13,23,33]))

Замена значений заполнителями:

INSERT INTO "table" (col1, col2, col3)
  VALUES (unnest(?), unnest(?), unnest(?))

Вы должны передать массивы или списки в качестве аргументов для этого запроса.Это означает, что вы можете делать огромные массовые вставки без конкатенации строк (и со всеми вытекающими отсюда опасностями: инъекцией sql и адским цитированием).

Массовое обновление

PostgreSQL добавил расширение FROM в UPDATE.Вы можете использовать его следующим образом:

update "table" 
  set value = data_table.new_value
  from 
    (select unnest(?) as key, unnest(?) as new_value) as data_table
  where "table".key = data_table.key;

В руководстве отсутствует хорошее объяснение, но в списке рассылки postgresql-admin есть пример.Я попытался уточнить это:

create table tmp
(
  id serial not null primary key,
  name text,
  age integer
);

insert into tmp (name,age) 
values ('keith', 43),('leslie', 40),('bexley', 19),('casey', 6);

update tmp set age = data_table.age
from
(select unnest(array['keith', 'leslie', 'bexley', 'casey']) as name, 
        unnest(array[44, 50, 10, 12]) as age) as data_table
where tmp.name = data_table.name;

В StackExchange также есть другие записей , объясняющие UPDATE...FROM.. с использованием предложения VALUES вместо подзапроса.Их легче читать, но они ограничены фиксированным числом строк.

14 голосов
/ 11 августа 2011

Я использовал 3 стратегии для пакетной транзакционной работы:

  1. Создайте операторы SQL на лету, объедините их с помощью точек с запятой, а затем отправьте операторы за один раз.Таким образом я выполнил до 100 вставок, и это было довольно эффективно (сделано против Postgres).
  2. JDBC имеет встроенные функции пакетной обработки, если они настроены.Если вы генерируете транзакции, вы можете очищать свои операторы JDBC, чтобы они выполнялись одним выстрелом.Эта тактика требует меньше обращений к базе данных, поскольку все операторы выполняются в одном пакете.
  3. Hibernate также поддерживает пакетную обработку JDBC по аналогии с предыдущим примером, но в этом случае вы выполняете метод flush() для HibernateSession, а не базовое соединение JDBC.Он выполняет то же самое, что и пакетная обработка JDBC.

Кстати, Hibernate также поддерживает стратегию пакетной обработки при получении коллекции.Если вы аннотируете коллекцию с помощью @BatchSize, при извлечении ассоциаций Hibernate будет использовать IN вместо =, что приведет к меньшему количеству операторов SELECT для загрузки коллекций.

12 голосов
/ 11 августа 2011

Массовая вставка может быть выполнена следующим образом:

INSERT INTO "table" ( col1, col2, col3)
  VALUES ( 1, 2, 3 ) , ( 3, 4, 5 ) , ( 6, 7, 8 );

Вставит 3 строки.

Множественное обновление определяется стандартом SQL, но не реализовано в PostgreSQL.

Цитата:

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

ОБНОВЛЕНИЕ учетных записей SET (contact_last_name, contact_first_name) = (ВЫБЕРИТЕ фамилию, имя_пользователя ОТ ГДЕ salesmen.id = accounts.sales_id); "

Ссылка: http://www.postgresql.org/docs/9.0/static/sql-update.html

2 голосов
/ 07 декабря 2016

довольно быстро заполнить json в наборе записей (postgresql 9.3 +)

big_list_of_tuples = [
    (1, "123.45"),
    ...
    (100000, "678.90"),
]

connection.execute("""
    UPDATE mytable
    SET myvalue = Q.myvalue
    FROM (
        SELECT (value->>0)::integer AS id, (value->>1)::decimal AS myvalue 
        FROM json_array_elements(%s)
    ) Q
    WHERE mytable.id = Q.id
    """, 
    [json.dumps(big_list_of_tuples)]
)
0 голосов
/ 11 августа 2011

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

...