Як видалити фіксовану кількість рядків із сортуванням у PostgreSQL?


107

Я намагаюся перенести деякі старі запити MySQL до PostgreSQL, але у мене виникають проблеми з цим:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

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

Так; як видалити фіксовану кількість рядків із сортуванням у PostgreSQL?

Редагувати: жоден первинний ключ означає, що немає log_idстовпця чи подібного. Ах, радощі застарілих систем!


1
Чому б не додати первинний ключ? Шматок пирога в postgresql : alter table foo add column id serial primary key.
— Уейн Конрад

Це був мій початковий підхід, але інші вимоги йому заважають.
— Whatsit

Відповіді:


159

Ви можете спробувати скористатися ctid:

DELETE FROM logtable
WHERE ctid IN (
    SELECT ctid
    FROM logtable
    ORDER BY timestamp
    LIMIT 10
)

Це ctid:

Фізичне розташування рядкової версії в межах її таблиці. Зауважте, що, хоча ви ctidможете використовувати для пошуку версії рядка дуже швидко, рядки ctidзміниться, якщо вона буде оновлена ​​або переміщена VACUUM FULL. Тому ctidмарно як довгостроковий ідентифікатор рядків.

Існує також, oidале це існує лише в тому випадку, якщо ви спеціально запитаєте про це під час створення таблиці.


Це працює, але наскільки це надійно? Чи є якісь "готчі", на які я повинен звернути увагу? Чи можливо для VACUUM FULLабо autovacuum викликати проблеми, якщо вони змінюють ctidзначення в таблиці під час запуску запиту?
— Whatsit

2
Зростання VACUUM не змінить ctids, я не думаю. Оскільки це просто ущільнюється у межах кожної сторінки, а ctid - це лише номер рядка, а не зміщення сторінки. Вакуумному ПОВНИЙ або операція Кластер б змінити CTID, але ці операції беруть доступу ексклюзивну блокування на таблицю першої.
— araqnid

@Whatsit: Моє враження від ctidдокументації полягає в тому, що ctidце досить стабільно, щоб зробити це DELETE нормально, але недостатньо стабільним, щоб, наприклад, помістити в іншу таблицю як FK-гетто. Імовірно, ви не ОНОВЛЮЄТЬСЯ, logtableтому вам не доведеться турбуватися про цю зміну ctids і VACUUM FULLзаблокує таблицю ( postgresql.org/docs/current/static/routine-vacuuming.html ), тому вам не доведеться турбуватися про інший спосіб, який ctidможна змінити. PostgreSQL-Fu @ araqnid досить сильний, і документи згодні з ним завантажуватися.
— mu занадто короткий

Дякую обом за роз’яснення. Я заглянув у документи, але не був певен, чи правильно їх інтерпретую. Я ніколи до цього не стикався з ctids.
— Whatsit

Це насправді є досить поганим рішенням, оскільки Postgres не в змозі використовувати TID-сканування для приєднання (IN - це окремий випадок). Якщо подивитися на план, він повинен бути досить жахливим. Тож "дуже швидко" застосовується лише тоді, коли ви чітко вказали CTID. Сказано, що версії 10.
— greatvovan

53

Документи Postgres рекомендують використовувати масив замість IN та підзапит. Це повинно працювати набагато швидше

DELETE FROM logtable 
WHERE id = any (array(SELECT id FROM logtable ORDER BY timestamp LIMIT 10));

Цей та деякі інші хитрощі можна знайти тут


@Konrad Garus Перейдіть за посиланням "Швидке видалення перших n рядків"
— критик

1
@BlakeRegalia Ні, оскільки в таблиці не вказаний первинний ключ. Це видалить усі рядки з "ідентифікатором", знайденим у першій 10. Якщо всі рядки мають однаковий ідентифікатор, усі рядки будуть видалені.
— Філіп Уайтхаус

6
Якщо any (array( ... ));швидше in ( ... )це звучить як помилка в оптимізаторі запитів - він повинен мати можливість помітити цю трансформацію і зробити те саме з самими даними.
— rjmunro

1
Я знайшов цей метод значно повільніше, ніж використання INна UPDATE(що може бути різницею).
— jmervine

1
Вимірювання в таблиці 12 ГБ: перший запит 450..1000 мс, другий 5..7 секунд: швидкий: видалення з cs_logging, де id = будь-який (масив (виберіть ідентифікатор з cs_logging, де date_create <зараз () - інтервал '1 день '* 30 та partition_key на зразок'% I 'упорядковуємо за лімітом ідентифікатора 500)) Повільний: видаліть із cs_logging, де id (виберіть ідентифікатор з cs_logging, де date_create <зараз () - інтервал' 1 дні '* 30 та partition_key, як'% Я замовляю за лімітом ідентифікатора 500). Використання ctid було набагато повільніше (хвилин).
— Гвідо Ліндерс


2

Припускаючи, що ви хочете видалити БУДЬ-10 записів (без замовлення), ви можете це зробити:

DELETE FROM logtable as t1 WHERE t1.ctid < (select t2.ctid from logtable as t2  where (Select count(*) from logtable t3  where t3.ctid < t2.ctid ) = 10 LIMIT 1);

У моєму випадку використання, видаливши записи 10М, це виявилося швидше.


1

Ви можете написати процедуру, яка перетворює цикл на видалення для окремих рядків, процедура може прийняти параметр, щоб вказати кількість елементів, які потрібно видалити. Але це трохи непосильне порівняно з MySQL.


0

Якщо у вас немає первинного ключа, ви можете використовувати масив синтаксису Where IN із складеним ключем.

delete from table1 where (schema,id,lac,cid) in (select schema,id,lac,cid from table1 where lac = 0 limit 1000);

Це працювало для мене.

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