У нас есть довольно специфическое приложение, которое использует PostgreSQL 8.3 в качестве бэкэнда хранилища (используя Python и psycopg2). Операции, которые мы выполняем над важными таблицами, в большинстве случаев вставляются или обновляются (редко удаляются или выбираются).
По соображениям здравого смысла мы создали наш собственный Data Mapper -подобный слой, который работает достаточно хорошо, но имеет одно большое узкое место - производительность обновления. Конечно, я не ожидаю, что сценарий обновления / замены будет таким же быстрым, как сценарий «вставка в пустую таблицу», но было бы неплохо подойти поближе.
Обратите внимание, что эта система не имеет одновременных обновлений
Мы всегда устанавливаем все поля каждой строки в обновлении, что можно увидеть в терминологии, где я использую слово «заменить» в своих тестах. До сих пор я пробовал два подхода к нашей проблеме обновления:
Создайте replace()
процедуру, которая принимает массив строк для обновления:
CREATE OR REPLACE FUNCTION replace_item(data item[]) RETURNS VOID AS $$
BEGIN
FOR i IN COALESCE(array_lower(data,1),0) .. COALESCE(array_upper(data,1),-1) LOOP
UPDATE item SET a0=data[i].a0,a1=data[i].a1,a2=data[i].a2 WHERE key=data[i].key;
END LOOP;
END;
$$ LANGUAGE plpgsql
Создайте правило insert_or_replace
, чтобы все, кроме случайного удаления, стало многострочными вставками
CREATE RULE "insert_or_replace" AS
ON INSERT TO "item"
WHERE EXISTS(SELECT 1 FROM item WHERE key=NEW.key)
DO INSTEAD
(UPDATE item SET a0=NEW.a0,a1=NEW.a1,a2=NEW.a2 WHERE key=NEW.key);
Оба эти параметра значительно ускоряют обновления, хотя последний немного замедляет вставки:
Multi-row insert : 50000 items inserted in 1.32 seconds averaging 37807.84 items/s
executemany() update : 50000 items updated in 26.67 seconds averaging 1874.57 items/s
update_andres : 50000 items updated in 3.84 seconds averaging 13028.51 items/s
update_merlin83 (i/d/i) : 50000 items updated in 1.29 seconds averaging 38780.46 items/s
update_merlin83 (i/u) : 50000 items updated in 1.24 seconds averaging 40313.28 items/s
replace_item() procedure : 50000 items replaced in 3.10 seconds averaging 16151.42 items/s
Multi-row insert_or_replace: 50000 items inserted in 2.73 seconds averaging 18296.30 items/s
Multi-row insert_or_replace: 50000 items replaced in 2.02 seconds averaging 24729.94 items/s
Случайные заметки о тестовом прогоне:
Итак, реальный вопрос: как я могу ускорить операции обновления / замены? (Я думаю, что эти выводы могут быть «достаточно хорошими», но я не хочу сдаваться, не касаясь ТАКОЙ толпы:)
Также кто-нибудь намекает на более элегантный replace_item (), или подтверждение того, что мои тесты полностью сломаны, было бы очень кстати.
Тестовый скрипт доступен здесь , если вы хотите попытаться воспроизвести. Не забудьте сначала проверить это ... хотя это работает ForMe, но ...
Вам нужно отредактировать строку db.connect () в соответствии с вашими настройками.
EDIT
Благодаря andres в #postgresql @ freenode у меня есть еще один тест с обновлением по одному запросу; очень похоже на многострочную вставку (указана выше как update_andres).
UPDATE item
SET a0=i.a0, a1=i.a1, a2=i.a2
FROM (VALUES ('00:00:00:00:00:01', 'v0', 'v1', 'v2'),
('00:00:00:00:00:02', 'v3', 'v4', 'v5'),
...
) AS i(key, a0, a1, a2)
WHERE item.key=i.key::macaddr
EDIT
Благодаря merlin83 в #postgresql @ freenode и jug / jwp ниже у меня есть еще один тест с подходом вставки в временную папку / удаления / вставки (указан как «update_merlin83 (i / d / i)» выше).
INSERT INTO temp_item (key, a0, a1, a2)
VALUES (
('00:00:00:00:00:01', 'v0', 'v1', 'v2'),
('00:00:00:00:00:02', 'v3', 'v4', 'v5'),
...);
DELETE FROM item
USING temp_item
WHERE item.key=temp_item.key;
INSERT INTO item (key, a0, a1, a2)
SELECT key, a0, a1, a2
FROM temp_item;
Мне кажется, что эти тесты не очень отражают производительность в реальном сценарии, но я думаю, что различия достаточно велики, чтобы дать представление о наиболее перспективных подходах для дальнейшего исследования. Скрипт perftest.py содержит все обновления для тех из вас, кто хочет его проверить. Хотя это довольно уродливо, так что не забывайте свои очки:)
EDIT
andres в #postgresql @ freenode указали, что я должен протестировать вариант вставки в темп / обновления (указанный выше как "update_merlin83 (i / u)").
INSERT INTO temp_item (key, a0, a1, a2)
VALUES (
('00:00:00:00:00:01', 'v0', 'v1', 'v2'),
('00:00:00:00:00:02', 'v3', 'v4', 'v5'),
...);
UPDATE item
SET a0=temp_item.a0, a1=temp_item.a1, a2=temp_item.a2
FROM temp_item
WHERE item.key=temp_item.key
EDIT
Вероятно, окончательное редактирование:
Я изменил свой сценарий, чтобы он лучше соответствовал нашему сценарию загрузки, и кажется, что цифры сохраняются даже при небольшом увеличении масштаба и добавлении некоторой случайности. Если кто-то получит совсем другие цифры из какого-то другого сценария, мне было бы интересно узнать об этом.