Как удалить фиксированное количество строк с сортировкой в ​​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 02

Ответы:


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или автовакууминг проблем , потому что если они изменяют ctidзначения в таблице, запрос работает?
Whatsit 02

2
Я не думаю, что добавочные ВАКУУМЫ не изменят ctid. Поскольку это просто сжимается на каждой странице, а ctid - это просто номер строки, а не смещение страницы. Вакуумном ПОЛНЫЙ или операция Кластер бы изменить CTID, но эти операции принимают доступа эксклюзивную блокировку на стол первым.
araqnid 02

@Whatsit: У меня сложилось впечатление, что ctidдокументация ctidдостаточно стабильна, чтобы этот DELETE работал нормально, но не достаточно стабильна, чтобы, например, поместить в другую таблицу как гетто-FK. Предположительно, вы не ОБНОВЛЯЕТЕ, logtableпоэтому вам не нужно беспокоиться об этом изменении ctids и VACUUM FULLзаблокировать таблицу ( postgresql.org/docs/current/static/routine-vacuuming.html ), поэтому вам не нужно беспокоиться о иначе это ctidможет измениться. PostgreSQL-Fu @araqnid довольно силен, и документация с ним согласна.
mu слишком короткое

Спасибо вам обоим за разъяснения. Я просмотрел документы, но не был уверен, что правильно их интерпретирую. До этого я никогда не сталкивался с ctids.
Whatsit 03

На самом деле это довольно плохое решение, поскольку 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 Нет, потому что в указанной таблице нет первичного ключа. Это удалит все строки с «ID», найденные в первых 10. Если все строки имеют одинаковый идентификатор, все строки будут удалены.
Филип Уайтхаус,

6
Если any (array( ... ));быстрее, чем in ( ... )это звучит как ошибка в оптимизаторе запросов - он должен уметь определять это преобразование и делать то же самое с самими данными.
rjmunro

1
Я нашел этот метод , чтобы быть значительно медленнее , чем при использовании INна UPDATE(который может быть разница).
jmervine

1
Измерение в таблице 12 ГБ: первый запрос 450..1000 мс, второй 5..7 секунд: Быстрый: удалить из cs_logging, где id = any (array (выбрать id из cs_logging, где date_created <now () - interval '1 days '* 30 и partition_key, как'% I ', упорядочивают по пределу идентификатора 500)) Медленный: удалить из cs_logging, где id in (выберите id из cs_logging, где date_created <now () - interval' 1 days '* 30 and partition_key like'% Заказываю по лимиту id 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.