Подтвердить что ты не робот

Массовое/пакетное обновление /upsert в PostgreSQL

Я пишу jango-ORM enchancement, который пытается кэшировать модели и откладывать сохранение модели до конца транзакции. Все это почти закончилось, однако я столкнулся с неожиданными трудностями в синтаксисе 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, поэтому некоторые хранимые процедуры также возможны здесь. Однако, поскольку я планирую вначале открыть исходный код проекта, более приветствуются любые переносимые идеи или способы сделать то же самое на другой РСУБД.

Последующий вопрос: Как выполнить пакетный оператор "insert-or-update" / "upsert"?

Результаты тестов

Я выполнил 100x раз 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 вводит существенные накладные расходы.

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

4b9b3361

Ответ 1

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

  • Генерировать операторы SQL "на лету", объединять их с запятой, а затем отправлять заявления одним выстрелом. Я сделал до 100 вставок таким образом, и это было довольно эффективно (сделано против Postgres).
  • JDBC имеет встроенные возможности пакетной обработки, если они настроены. Если вы создаете транзакции, вы можете сбросить свои JDBC-заявления, чтобы они совершали транзакции одним выстрелом. Эта тактика требует меньше вызовов в базе данных, поскольку все операторы выполняются в одной партии.
  • Hibernate также поддерживает JDBC-пакет по линиям предыдущего примера, но в этом случае вы выполняете метод flush() для Hibernate Session, а не для основного JDBC-соединения. Он выполняет то же самое, что и JDBC-пакет.

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

Ответ 2

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

Вы можете изменить объемную вставку из трех столбцов 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 вместо подзапрос. Они могут быть легче прочитаны, но ограничены фиксированным числом строк.

Ответ 3

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

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

Вставляет 3 строки.

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

Цитата:

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

Учетные записи UPDATE SET (contact_last_name, contact_first_name) =     (SELECT last_name, first_name FROM salesmen      WHERE salesmen.id = accounts.sales_id);

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

Ответ 4

довольно быстро заселить 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)]
)

Ответ 5

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