Дуже повільний DELETE в PostgreSQL, вирішення?


30

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

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

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

Чи було б набагато швидше, якби я написав сценарій, який перегляне залежні таблиці, починаючи від таблиці, віддалену від центральної таблиці, видаляючи залежні рядки таблиці за таблицею?

Важливою деталлю є те, що на деяких таблицях є тригери.


4
Через 5 років я змінюю прийняту відповідь. Повільні DELETE майже завжди викликані відсутніми індексами на сторонніх ключах, які прямо чи опосередковано посилаються на таблицю, з якої видалено. Тригери, які спрацьовують на операторах DELETE, також можуть уповільнити ситуацію, хоча рішення майже завжди змушує їх працювати швидше (наприклад, додаючи відсутні індекси) і майже ніколи не відключати всі тригери.
jd.

Відповіді:


30

У мене була схожа проблема. Як виявляється, ці ON DELETE CASCADEтригери дуже сповільнили справи, тому що ці каскадні видалення були жахливо повільними.

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


Ого, це допомогло мені видалити записи 8М за кілька хвилин. Але я не розумію, що моя таблиця містила лише посилання на інші таблиці, жодна таблиця не містить посилань на мою таблицю. То який саме ефект тут? (Я не використовую ON DELETE CASCADE)
msrd0

2
Це вирішило це і для мене. Для всіх, хто намагається це зробити, ви можете зробити EXPLAIN (ANALYZE, BUFFERS)запит на видаленні одного рядка, і він повинен показати вам, які обмеження зовнішніх ключів тривали найдовше (принаймні, це було для мене).
Джастін Уордман

Це ж довелося видалити на каскадному 600k рядків, і на початку це займало від 2-10 за операцію при 100% використанні процесора. Тепер їх було потрібно лише кілька хвилин, щоб видалити їх усіх із 80% використанням процесора.
fillobotto

Важливо зауважити, що якщо у вас є закордонна посилання на будь-яке місце, стовпець джерела повинен мати реальний індекс, або продуктивність постраждає. Я не впевнений, чи PRIMARYдостатньо UNIQUEіндексу, але індекс, безумовно, недостатньо хороший для цієї мети.
Мікко Ранталайнен

26

У вас є кілька варіантів. Найкращий варіант - запустити пакетне видалення, щоб тригери не потрапили. Вимкніть тригери перед видаленням, а потім знову ввімкніть їх. Це економить вам дуже велику кількість часу. Наприклад:

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

Важливим моментом тут є те, що ви хочете мінімізувати глибину підзапитів. У цьому випадку ви можете налаштувати темп-таблиці для зберігання відповідної інформації, щоб уникнути глибоких підзапитів під час видалення.


У моєму випадку я запустив команду DELETE FROM перед сном, і це все ще не було зроблено, коли я повернувся до свого комп'ютера наступного дня. 100% використання процесора весь час на одному ядрі. Після відключення тригерів та повторної спроби видалення записів на 200 тис. Знадобилося 3 секунди. Дякую!
Нік Вудгемс

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 / rollbackблок, щоб ви могли порівняти новий час виконання з попереднім. Продовжуйте, поки ви не будете задоволені часом відповіді на видалення одного рядка (я отримав один запит - від 25,6 секунди до 15 мс, просто додавши різні індекси). Потім ви можете приступити до повного видалення без будь-яких злому.

(Зверніть увагу, що EXPLAINпотрібен запит, який можна успішно виконати. У мене колись виникла проблема, коли PostgreSQL зайняв занадто багато часу, щоб зрозуміти, що одне видалення порушить обмеження іноземного ключа, і в такому випадку EXPLAINйого не можна використовувати, оскільки воно не випромінює терміни для помилки запити. Я не знаю простого способу налагодження проблем продуктивності в такому випадку.)


8

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

Виконати DELETE тут.

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

Джерело тут.


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)
);

У цьому випадку видаліть дані у tablec, а потім tableb та tablea

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

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

$$ LANGUAGE SQL;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.