ฉันจะลบจำนวนแถวคงที่ด้วยการเรียงลำดับใน PostgreSQL ได้อย่างไร


108

ฉันกำลังพยายามพอร์ตการสืบค้น MySQL เก่าไปยัง PostgreSQL แต่ฉันมีปัญหากับคำถามนี้:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL ไม่อนุญาตให้มีการสั่งซื้อหรือ จำกัด ไวยากรณ์การลบและตารางไม่มีคีย์หลักดังนั้นฉันจึงไม่สามารถใช้เคียวรีย่อยได้ นอกจากนี้ผมต้องการที่จะรักษาพฤติกรรมที่แบบสอบถามลบว่าจำนวนที่กำหนดหรือบันทึก - ตัวอย่างเช่นถ้าตารางมี 30 แถว แต่พวกเขาทั้งหมดมีการประทับเวลาเดียวกันผมยังคงต้องการที่จะลบ 10 แม้ว่ามันจะไม่ได้เรื่อง ซึ่ง 10.

ดังนั้น; ฉันจะลบจำนวนแถวคงที่ด้วยการเรียงลำดับใน PostgreSQL ได้อย่างไร

แก้ไข:ไม่มีคีย์หลักหมายความว่าไม่มีlog_idคอลัมน์หรือคล้ายกัน อาความสุขของระบบเดิม!


1
ทำไมไม่เพิ่มคีย์หลัก? ชิ้น o' เค้กใน alter table foo add column id serial primary keyPostgreSQL:
Wayne Conrad

นั่นเป็นแนวทางเริ่มต้นของฉัน แต่ข้อกำหนดอื่น ๆ ป้องกันไม่ให้
Whatsit

คำตอบ:


160

คุณสามารถลองใช้ctid:

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

ctidคือ:

ตำแหน่งทางกายภาพของเวอร์ชันแถวภายในตาราง หมายเหตุว่าแม้ว่าctidจะสามารถใช้ในการค้นหารุ่นแถวอย่างรวดเร็วเป็นแถวจะมีการเปลี่ยนแปลงหากมีการปรับปรุงหรือย้ายโดยctid VACUUM FULLดังนั้นจึงctidไม่มีประโยชน์เป็นตัวระบุแถวระยะยาว

นอกจากนี้ยังoidมีอยู่ก็ต่อเมื่อคุณขอเป็นพิเศษเมื่อคุณสร้างตาราง


วิธีนี้ใช้งานได้ แต่มันน่าเชื่อถือแค่ไหน? มี 'gotchas' ที่ฉันต้องระวังหรือไม่? เป็นไปได้VACUUM FULLหรือไม่ที่ระบบจะทำให้เกิดปัญหาโดยอัตโนมัติหากเปลี่ยนctidค่าในตารางขณะที่แบบสอบถามกำลังทำงานอยู่
Whatsit

2
VACUUM ที่เพิ่มขึ้นจะไม่เปลี่ยน ctids ฉันไม่คิดว่า เนื่องจากมีเพียงแค่กระชับภายในแต่ละหน้าและ ctid เป็นเพียงหมายเลขบรรทัดไม่ใช่ออฟเซ็ตของหน้า การดำเนินการ VACUUM FULL หรือ CLUSTER จะเปลี่ยน ctid แต่การดำเนินการเหล่านั้นจะใช้การล็อกพิเศษเฉพาะการเข้าถึงบนตารางก่อน
araqnid

@Whatsit: ความประทับใจของฉันเกี่ยวกับctidเอกสารคือctidมีความเสถียรเพียงพอที่จะทำให้ DELETE นี้ทำงานได้ดี แต่ไม่เสถียรพอที่จะวางในตารางอื่นเป็นสลัม -FK สันนิษฐานว่าคุณไม่ได้อัปเดตlogtableดังนั้นคุณจึงไม่ต้องกังวลกับการเปลี่ยนแปลงนั้นctidและVACUUM FULLจะล็อกตาราง ( postgresql.org/docs/current/static/routine-vacuuming.html ) ดังนั้นคุณจึงไม่ต้องกังวล วิธีอื่นที่ctidสามารถเปลี่ยนแปลงได้ PostgreSQL-Fu ของ @ araqnid ค่อนข้างแข็งแกร่งและเอกสารก็เห็นด้วยกับเขาที่จะบูต
สั้นเกินไป

ขอบคุณทั้งสองท่านสำหรับคำชี้แจง ฉันดูเอกสารแล้ว แต่ฉันไม่แน่ใจว่าฉันตีความถูกต้อง ฉันไม่เคยพบ 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 ที่นี่คุณไปที่ลิงค์ 'Fast first n แถวลบ'
วิจารณ์

1
@BlakeRegalia ไม่เพราะไม่มีคีย์หลักในตารางที่ระบุ การดำเนินการนี้จะลบแถวทั้งหมดที่มี "ID" ที่พบใน 10 รายการแรกหากแถวทั้งหมดมี ID เดียวกันทุกแถวจะถูกลบ
Philip Whitehouse

6
หากany (array( ... ));เร็วกว่าin ( ... )นั้นจะดูเหมือนเป็นข้อบกพร่องในเครื่องมือเพิ่มประสิทธิภาพการสืบค้น - ควรจะสามารถระบุการเปลี่ยนแปลงนั้นและทำสิ่งเดียวกันกับข้อมูลเอง
rjmunro

1
ฉันพบว่าวิธีนี้ช้ากว่าการใช้INกับ an UPDATE(ซึ่งอาจแตกต่างกันมาก)
jmervine

1
การวัดบนตาราง 12 GB: แบบสอบถามแรก 450..1000 ms วินาทีหนึ่ง 5..7 วินาที: อย่างรวดเร็วหนึ่ง: ลบจาก cs_logging โดยที่ id = ใด ๆ (อาร์เรย์ (เลือก id จาก cs_logging โดยที่ date_created <now () - ช่วงเวลา '1 วัน '* 30 และ partition_key เช่น'% I 'order by id limit 500)) Slow one: ลบจาก cs_logging โดย id in (เลือก id จาก cs_logging โดยที่ date_created <now () - ช่วงเวลา' 1 วัน '* 30 และ partition_key เช่น'% ฉันสั่งโดย จำกัด 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);

สำหรับกรณีการใช้งานของฉันการลบระเบียน 10M สิ่งนี้กลายเป็นเรื่องที่เร็วกว่า


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.