Làm cách nào để xóa một số hàng cố định với sắp xếp trong PostgreSQL?


107

Tôi đang cố gắng chuyển một số truy vấn MySQL cũ sang PostgreSQL, nhưng tôi đang gặp sự cố với câu này:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL không cho phép sắp xếp thứ tự hoặc giới hạn trong cú pháp xóa của nó và bảng không có khóa chính nên tôi không thể sử dụng truy vấn con. Ngoài ra, tôi muốn duy trì hành vi nơi truy vấn xóa chính xác số hoặc bản ghi đã cho - ví dụ: nếu bảng chứa 30 hàng nhưng tất cả chúng đều có cùng dấu thời gian, tôi vẫn muốn xóa 10, mặc dù điều đó không quan trọng mà 10.

Vì thế; làm cách nào để xóa một số hàng cố định có sắp xếp trong PostgreSQL?

Chỉnh sửa: Không có khóa chính nghĩa là không có log_idcột hoặc cột tương tự. Ah, niềm vui của các hệ thống kế thừa!


1
Tại sao không thêm khóa chính? Piece o 'cake trong postgresql : alter table foo add column id serial primary key.
Wayne Conrad

Đó là cách tiếp cận ban đầu của tôi, nhưng các yêu cầu khác ngăn cản nó.
Whatsit

Câu trả lời:


159

Bạn có thể thử sử dụng ctid:

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

ctid:

Vị trí thực của phiên bản hàng trong bảng của nó. Lưu ý rằng mặc dù ctidcó thể được sử dụng để xác định phiên bản hàng rất nhanh chóng, nhưng một hàng ctidsẽ thay đổi nếu nó được cập nhật hoặc di chuyển bởi VACUUM FULL. Do đó, ctidvô dụng như một định danh hàng dài hạn.

Cũng có oidnhưng điều đó chỉ tồn tại nếu bạn yêu cầu cụ thể khi tạo bảng.


Điều này hoạt động, nhưng nó đáng tin cậy như thế nào? Có 'gotchas' nào tôi cần để ý không? Có khả năng VACUUM FULLhoặc autovacuum gây ra sự cố nếu chúng thay đổi các ctidgiá trị trong bảng khi truy vấn đang chạy không?
Whatsit

2
VACUUM tăng dần sẽ không thay đổi ctids, tôi không nghĩ vậy. Vì nó chỉ nằm gọn trong mỗi trang và ctid chỉ là số dòng không phải là phần bù trang. Một hoạt động VACUUM FULL hoặc một CLUSTER sẽ thay đổi ctid, nhưng những hoạt động đó sẽ có một khóa độc quyền truy cập trên bảng trước.
araqnid

@Whatsit: Ấn tượng của tôi về ctidtài liệu là ctidđủ ổn định để làm cho DELETE này hoạt động ổn nhưng không đủ ổn định, chẳng hạn như đưa vào một bảng khác như một khu ổ chuột-FK. Có lẽ bạn không CẬP NHẬT logtablevì vậy bạn không phải lo lắng về việc thay đổi ctids và VACUUM FULLcó khóa bảng ( postgresql.org/docs/current/static/routine-vacuuming.html ) nên bạn không phải lo lắng về theo cách khác mà ctids có thể thay đổi. PostgreSQL-Fu của @ araqnid khá mạnh và các tài liệu đồng ý với anh ấy để khởi động.
mu quá ngắn

Cảm ơn cả hai bạn đã làm rõ. Tôi đã xem xét các tài liệu nhưng tôi không chắc mình đã diễn giải chúng một cách chính xác. Tôi chưa bao giờ gặp ctids trước đây.
Whatsit

Đây thực sự là một giải pháp khá tệ vì Postgres không thể sử dụng tính năng quét TID trong các phép nối (IN là một trường hợp cụ thể của nó). Nếu bạn nhìn vào kế hoạch, nó sẽ khá khủng khiếp. Vì vậy, "rất nhanh" chỉ áp dụng khi bạn chỉ định CTID rõ ràng. Cho biết là như các phiên bản 10
greatvovan

53

Tài liệu Postgres khuyên bạn nên sử dụng mảng thay vì IN và truy vấn con. Điều này sẽ hoạt động nhanh hơn nhiều

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

Điều này và một số thủ thuật khác có thể được tìm thấy tại đây


@Konrad Garus Đây là liên kết của bạn , 'Xóa nhanh n hàng đầu tiên'
phê bình

1
@BlakeRegalia Không, vì không có khóa chính nào trong bảng được chỉ định. Thao tác này sẽ xóa tất cả các hàng có "ID" được tìm thấy trong 10 đầu tiên. Nếu tất cả các hàng có cùng một ID thì tất cả các hàng sẽ bị xóa.
Philip Whitehouse

6
Nếu any (array( ... ));nhanh hơn mức in ( ... )đó nghe có vẻ như một lỗi trong trình tối ưu hóa truy vấn - nó sẽ có thể phát hiện ra sự chuyển đổi đó và thực hiện điều tương tự với chính dữ liệu.
rjmunro

1
Tôi thấy phương pháp này chậm hơn đáng kể so với sử dụng INtrên một UPDATE(có thể là sự khác biệt).
jmervine

1
Phép đo trên bảng 12 GB: truy vấn đầu tiên 450..1000 mili giây, truy vấn thứ hai 5..7 giây: Truy vấn nhanh: xóa khỏi cs_logging nơi id = bất kỳ (mảng (chọn id từ cs_logging trong đó date_create <now () - khoảng thời gian '1 ngày '* 30 và partition_key như'% I 'sắp xếp theo id giới hạn 500)) Chậm: xóa khỏi cs_logging nơi có id trong (chọn id từ cs_logging nơi date_create <now () - khoảng thời gian' 1 ngày '* 30 và phân vùng_ khóa như'% Tôi đặt hàng theo giới hạn id 500). Sử dụng ctid chậm hơn rất nhiều (phút).
Guido Leenders

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

2

Giả sử bạn muốn xóa BẤT KỲ 10 bản ghi nào (không cần thứ tự), bạn có thể thực hiện điều này:

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

Đối với trường hợp sử dụng của tôi, xóa 10 triệu bản ghi, điều này hóa ra nhanh hơn.


1

Bạn có thể viết một thủ tục lặp lại quá trình xóa cho từng dòng riêng lẻ, thủ tục có thể nhận một tham số để chỉ định số lượng mục bạn muốn xóa. Nhưng đó là một chút quá mức cần thiết so với MySQL.


0

Nếu bạn không có khóa chính, bạn có thể sử dụng cú pháp mảng Where IN với khóa tổng hợp.

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

Điều này đã làm việc cho tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.