ฉันจะลบจำนวนแถวคงที่ด้วยการเรียงลำดับใน 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.