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.
Wayne Conrad 2011 년

이것이 나의 초기 접근 방식 이었지만 다른 요구 사항이이를 방지합니다.
Whatsit 2011 년

답변:


159

다음을 사용해 볼 수 있습니다 ctid.

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

ctid것입니다 :

테이블 내 행 버전의 물리적 위치입니다. 를 ctid사용하여 행 버전을 매우 빠르게 찾을 수 있지만 행이 ctid.NET에 의해 업데이트되거나 이동되면 행 이 변경됩니다 VACUUM FULL. 따라서 ctid장기 행 식별자로 쓸모가 없습니다.

또한 oid테이블을 만들 때 특별히 요청하는 경우에만 존재합니다.


이것은 작동하지만 얼마나 신뢰할 수 있습니까? 주의해야 할 '잘못'이 있습니까? 쿼리가 실행되는 동안 테이블 VACUUM FULLctid값 을 변경하면 for 또는 autovacuum이 문제를 일으킬 수 있습니까?
Whatsit 2011 년

2
증분 진공은 ctid를 변경하지 않을 것입니다. 그것은 각 페이지 내에서 압축되고 ctid는 페이지 오프셋이 아닌 줄 번호입니다. VACUUM FULL 또는 CLUSTER 작업 ctid를 변경하지만 이러한 작업은 먼저 테이블에 대한 액세스 배타적 잠금을 취합니다.
araqnid 2011 년

@Whatsit : ctid문서 에 대한 나의 인상은 ctid이 DELETE가 제대로 작동하도록 충분히 안정적이지만 예를 들어 빈민가 -FK로 다른 테이블에 넣을만큼 안정적이지 않다는 것입니다. 아마도 당신은 업데이트되지 않습니다 logtable당신이 변경에 대한 걱정을하지 않아도 ctids와 VACUUM FULL테이블 (고정하지 postgresql.org/docs/current/static/routine-vacuuming.html을 당신이 걱정할 필요가 없습니다) ctids가 변경할 수 있는 다른 방법입니다 . @araqnid의 PostgreSQL-Fu는 매우 강력하며 문서는 부팅에 동의합니다.
MU이 너무 짧

설명해 주신 두 분께 감사드립니다. 문서를 살펴 보았지만 올바르게 해석하고 있는지 확신 할 수 없었습니다. 나는이 전에 ctid를 본 적이 없습니다.
Whatsit 2011 년

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 개에서 "ID"가있는 모든 행이 삭제됩니다. 모든 행의 ID가 동일한 경우 모든 행이 삭제됩니다.
Philip Whitehouse

6
경우 any (array( ... ));더 빨리보다 in ( ... )쿼리 최적화 버그 같은 그 소리 - 그 변화를 발견 할 수 및 데이터 자체와 같은 일을해야한다.
rjmunro

1
이 방법 INUPDATE(차이가있을 수 있음)에서 사용 하는 것보다 훨씬 느립니다 .
jmervine

1
12GB 테이블에서 측정 : 첫 번째 쿼리 450..1000ms, 두 번째 쿼리 5..7 초 : 빠른 쿼리 : cs_logging에서 삭제 여기서 id = any (array (select id from cs_logging where date_created <now ()-interval '1 days '* 30 및 partition_key like'% I 'order by id limit 500)) Slow one : delete from cs_logging where id in (select id from cs_logging where date_created <now ()-interval'1 days '* 30 and partition_key like'% 나는 ID 제한 500)으로 주문합니다. ctid 사용은 훨씬 느 렸습니다 (분).
Guido Leenders


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

내 사용 사례에서 1 천만 개의 레코드를 삭제하면 더 빠른 것으로 판명되었습니다.


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

이것은 나를 위해 일했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.