Очень медленное удаление в PostgreSQL, обходной путь?

30

У меня есть база данных на PostgreSQL 9.2, которая имеет основную схему с около 70 таблицами и переменное количество одинаково структурированных схем для каждого клиента по 30 таблиц в каждой. Клиентские схемы имеют внешние ключи, ссылающиеся на основную схему, а не наоборот.

Я только начал заполнять базу данных реальными данными, взятыми из предыдущей версии. Размер БД достиг примерно 1,5 ГБ (ожидается, что он увеличится до нескольких десятков ГБ в течение нескольких недель), когда мне пришлось выполнять массовое удаление в центральной таблице основной схемы. Все соответствующие внешние ключи отмечены на УДАЛЕННОМ КАСКАДЕ.

Неудивительно, что это займет много времени, но через 12 часов стало ясно, что мне лучше начать заново, сбросить БД и снова запустить миграцию. Но что, если мне нужно будет повторить эту операцию позже, когда БД жива и намного больше? Есть ли альтернативные, более быстрые методы?

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

Важной деталью является то, что на некоторых таблицах есть триггеры.

юлианский день.
источник
4
Через 5 лет я меняю принятый ответ. Медленные УДАЛЕНИЯ почти всегда вызваны отсутствием индексов на внешних ключах, которые прямо или косвенно ссылаются на удаляемую таблицу. Триггеры, которые запускаются в операторах DELETE, также могут замедлять работу, хотя решение почти всегда состоит в том, чтобы заставить их работать быстрее (например, путем добавления отсутствующих индексов) и почти никогда не отключать все триггеры.
JD.

Ответы:

30

У меня была аналогичная проблема. Как оказалось, эти ON DELETE CASCADEтриггеры немного замедлили процесс, потому что эти каскадные удаления были ужасно медленными.

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

ailnlv
источник
Вау, это помогло мне удалить 8M записей за несколько минут. Но что я не понимаю, так это то, что моя таблица содержит ссылки только на другие таблицы, никакие другие таблицы не содержат ссылок на мою таблицу. Так что именно здесь происходит? (Я не использую ON DELETE CASCADE)
msrd0
2
Это решило это и для меня. Для любого, кто пытается это сделать, вы можете сделать EXPLAIN (ANALYZE, BUFFERS)запрос на удаление одной строки, и он должен показать вам, какие ограничения внешнего ключа потребовали больше всего времени (по крайней мере, для меня).
Джастин Воркман
То же самое, пришлось удалить на каскаде 600 тыс. Строк, и в начале требовалось от 2 до 10 на операцию при 100% загрузке ЦП. Теперь их удаление заняло всего несколько минут при 80% загрузки процессора.
Филлоботто
Важно отметить, что если у вас есть внешняя ссылка куда-либо, исходный столбец должен иметь реальный индекс, иначе производительность пострадает. Я не уверен, достаточно ли PRIMARYиндекса, но UNIQUEиндекс определенно недостаточно хорош для этой цели.
Микко Ранталайнен
26

У вас есть несколько вариантов. Лучший вариант - запустить пакетное удаление, чтобы триггеры не срабатывали. Отключите триггеры перед удалением, затем снова включите их. Это экономит вам очень много времени. Например:

ALTER TABLE tablename DISABLE TRIGGER ALL; 
DELETE ...; 
ALTER TABLE tablename ENABLE TRIGGER ALL;

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

Крис Траверс
источник
В моем случае я запустил команду «УДАЛИТЬ ОТ» перед сном, но это не было сделано, когда я вернулся на свой компьютер на следующий день. 100% загрузка ЦП на одном ядре все время. После отключения триггеров и повторной попытки потребовалось 3 секунды, чтобы удалить 200 тыс. Записей. Спасибо!
Ник Вудхамс,
13

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

delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';

Вместо того, чтобы действительно запустить эту команду, вы можете сделать

begin;
explain (analyze,buffers,timing) delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';
rollback;

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

...
Trigger for constraint XYZ123: time=12311.292 calls=1
...

Значение timeв мс (миллисекунде), поэтому проверка этого противопоказания заняла около 12,3 секунды. Вам нужно добавить новый INDEXнад необходимыми столбцами, чтобы этот триггер мог быть эффективно вычислен. Для ссылок на внешний ключ столбец, который ссылается на другую таблицу, должен быть проиндексирован (т. Е. Исходный столбец, а не целевой столбец). PostgreSQL не создает автоматически такие индексы автоматически и DELETEявляется единственным распространенным запросом, где вам действительно нужен этот индекс. В результате вы, возможно, накопили данные за годы, пока не дойдете до случая, когда DELETEслишком медленно из-за отсутствия индекса.

После того, как вы зафиксировали производительность этого ограничения (или что-то еще, что заняло слишком много времени), повторите команду в begin/ rollbackblock, чтобы вы могли сравнить новое время выполнения с предыдущим. Продолжайте до тех пор, пока вы не будете довольны временем отклика на удаление одной строки (мне нужно было выполнить один запрос от 25,6 до 15 мс, просто добавив разные индексы). Затем вы можете продолжить полное удаление без каких-либо взломов.

(Обратите внимание, что EXPLAINнужен запрос, который может успешно завершиться. У меня когда-то была проблема, когда PostgreSQL слишком долго выяснял, что одно удаление будет нарушать ограничение внешнего ключа, и в этом случае EXPLAINего нельзя использовать, поскольку он не будет генерировать синхронизацию для сбойного запросы. Я не знаю простой способ отладки проблем с производительностью в таком случае.)

Микко Ранталайнен
источник
8

Отключение триггеров может угрожать целостности БД и не может быть рекомендовано; однако если вы уверены, что ваша операция защищена от ограничений, вы можете отключить триггеры следующим образом:SET session_replication_role = replica;

Запустите DELETEздесь.

Чтобы восстановить триггеры, запустите: SET session_replication_role = DEFAULT;

Источник здесь.

Pinimo
источник
0

Если у вас есть триггеры ON DELETE CASCADE, мы надеемся, что они присутствуют по какой-то причине и поэтому не должны быть отключены. Другой прием (все еще добавляющий ваши индексы), который работает для меня, заключается в создании функции удаления, которая вручную удаляет данные, начиная с таблиц в конце каскада, и работает в направлении основной таблицы. (Это то же самое, что и при наличии триггера ON DELETE RESTRICT)

CREATE TABLE tablea (
    tablea_uid integer
);

CREATE TABLE tableb (
    tableb_uid integer,
    tablea_rid integer REFERENCES tablea(tablea_uid)
);

CREATE TABLE tablec (
    tablec_uid integer,
    tableb_rid integer REFERENCES tableb(tableb_uid)
);

В этом случае удалите данные в таблице, затем в таблице, затем в таблице.

CREATE OR REPLACE FUNCTION delete_in_order()
 RETURNS void AS $$

    DELETE FROM tablec;
    DELETE FROM tableb;
    DELETE FROM tablea;

$$ LANGUAGE SQL;
blindguy
источник