Jak usunąć ustaloną liczbę wierszy z sortowaniem w PostgreSQL?


107

Próbuję przenieść niektóre stare zapytania MySQL do PostgreSQL, ale mam problem z tym:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL nie pozwala na porządkowanie ani ograniczenia w swojej składni usuwania, a tabela nie ma klucza podstawowego, więc nie mogę użyć podzapytania. Dodatkowo chcę zachować zachowanie, w którym zapytanie usuwa dokładnie podaną liczbę lub rekordy - na przykład, jeśli tabela zawiera 30 wierszy, ale wszystkie mają ten sam znacznik czasu, nadal chcę usunąć 10, chociaż nie ma to znaczenia który 10.

Więc; jak usunąć określoną liczbę wierszy z sortowaniem w PostgreSQL?

Edycja: brak klucza podstawowego oznacza brak log_idkolumny lub podobnej. Ach, radości ze starszych systemów!


1
Dlaczego nie dodać klucza podstawowego? Kawałek o”ciasto postgresql: alter table foo add column id serial primary key.
Wayne Conrad

Takie było moje początkowe podejście, ale inne wymagania temu zapobiegają.
Whatsit

Odpowiedzi:


159

Możesz spróbować użyć ctid:

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

ctidJest:

Fizyczna lokalizacja wersji wiersza w jego tabeli. Zauważ, że chociaż ctidmożna użyć do bardzo szybkiego zlokalizowania wersji wiersza, wiersz ctidzmieni się, jeśli zostanie zaktualizowany lub przeniesiony przez VACUUM FULL. Dlatego ctidjest bezużyteczny jako długoterminowy identyfikator wiersza.

Istnieje również, oidale istnieje tylko wtedy, gdy specjalnie o to poprosisz podczas tworzenia tabeli.


To działa, ale na ile jest to niezawodne? Czy są jakieś „pułapki”, na które muszę uważać? Czy możliwe jest automatyczne VACUUM FULLodkurzanie lub wywoływanie problemów, jeśli zmieniają ctidwartości w tabeli w trakcie wykonywania zapytania?
Whatsit

2
Przyrostowe ODKURZACZE nie zmienią ctids, nie sądzę. Ponieważ to tylko kompaktuje na każdej stronie, a ctid to tylko numer wiersza, a nie przesunięcie strony. Próżni całości lub operacja KLASTER by zmienić ctid, ale te działania mają dostęp wyłączną blokadę na stole w pierwszej kolejności.
araqnid

@Whatsit: Mam wrażenie, że ctiddokumentacja jest ctidwystarczająco stabilna, aby to DELETE działało poprawnie, ale nie jest wystarczająco stabilna, aby na przykład umieścić w innej tabeli jako getto-FK. Prawdopodobnie nie AKTUALIZUJESZ, logtablewięc nie musisz się martwić o zmianę ctids i VACUUM FULLblokujesz tabelę ( postgresql.org/docs/current/static/routine-vacuuming.html ), więc nie musisz się martwić inny sposób, który ctidmoże się zmienić. PostgreSQL-Fu @ araqnid jest dość mocny i dokumentacja zgadza się z nim, aby uruchomić.
mu jest za krótkie

Dziękuję wam obojgu za wyjaśnienie. Zajrzałem do doktorów, ale nie byłem pewien, że interpretowałem ich poprawnie. Nigdy wcześniej nie spotkałem ctids.
Whatsit

W rzeczywistości jest to całkiem złe rozwiązanie, ponieważ Postgres nie jest w stanie użyć skanowania TID w połączeniach (szczególnym przypadkiem jest IN). Jeśli spojrzysz na plan, powinien być dość okropny. Tak więc „bardzo szybko” ma zastosowanie tylko wtedy, gdy wyraźnie określisz CTID. Wspomniany jest od wersji 10.
greatvovan,

53

Dokumenty Postgres zalecają używanie tablicy zamiast IN i podzapytania. To powinno działać znacznie szybciej

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

To i kilka innych sztuczek można znaleźć tutaj


@Konrad Garus Here you go link , „Szybkie usuwanie pierwszych n wierszy”
krytyk

1
@BlakeRegalia Nie, ponieważ w określonej tabeli nie ma klucza podstawowego. Spowoduje to usunięcie wszystkich wierszy z „ID” znalezionych w pierwszych 10. Jeśli wszystkie wiersze mają ten sam identyfikator, wszystkie wiersze zostaną usunięte.
Philip Whitehouse

6
Jeśli any (array( ... ));jest szybszy niż in ( ... )to, brzmi jak błąd w optymalizatorze zapytań - powinien być w stanie wykryć tę transformację i zrobić to samo z samymi danymi.
rjmunro

1
Okazało się, że ta metoda jest znacznie wolniejsza niż używanie INna UPDATE(co może być różnicą).
jmervine

1
Pomiar na tabeli 12 GB: pierwsze zapytanie 450..1000 ms, drugie 5..7 sekund: Szybkie: usuń z cs_logging gdzie id = any (array (wybierz id z cs_logging gdzie date_created <now () - interwał '1 dni '* 30 i partycja_klucz jak'% I 'kolejność według id limitu 500)) Wolny: usuń z cs_logging gdzie id w (wybierz id z cs_logging gdzie date_created <now () - interwał' 1 dni '* 30 i partycji jak'% Zamawiam według id limitu 500). Używanie ctid było dużo wolniejsze (minuty).
Guido Leenders

14
delete from logtable where log_id in (
    select log_id from logtable order by timestamp limit 10);

2

Zakładając, że chcesz usunąć JAKIEKOLWIEK 10 rekordów (bez zamawiania), możesz to zrobić:

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

W moim przypadku usunięcia 10M rekordów okazało się to szybsze.


1

Mógłbyś napisać procedurę, która zapętla usuwanie dla poszczególnych linii, procedura może przyjmować parametr określający liczbę elementów, które chcesz usunąć. Ale to trochę przesada w porównaniu z MySQL.


0

Jeśli nie masz klucza podstawowego, możesz użyć składni tablicy Where IN z kluczem złożonym.

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

To zadziałało dla mnie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.