เคียวรีที่มีประสิทธิภาพเพื่อรับค่าสูงสุดต่อกลุ่มจากตารางขนาดใหญ่


14

รับตาราง:

    Column    |            Type             
 id           | integer                     
 latitude     | numeric(9,6)                
 longitude    | numeric(9,6)                
 speed        | integer                     
 equipment_id | integer                     
 created_at   | timestamp without time zone
Indexes:
    "geoposition_records_pkey" PRIMARY KEY, btree (id)

ตารางมี 20 ล้านบันทึกที่ไม่ได้พูดค่อนข้างมาก แต่มันทำให้การสแกนตามลำดับช้าลง

ฉันจะได้รับบันทึกสุดท้ายmax(created_at)ของแต่ละรายการได้equipment_idอย่างไร

ฉันได้ลองค้นหาทั้งสองข้อต่อไปนี้โดยมีหลายรุ่นที่ฉันได้อ่านจากคำตอบของหัวข้อนี้:

select max(created_at),equipment_id from geoposition_records group by equipment_id;

select distinct on (equipment_id) equipment_id,created_at 
  from geoposition_records order by equipment_id, created_at desc;

ฉันได้ลองสร้างดัชนี btree ด้วยequipment_id,created_atแต่ Postgres พบว่าการใช้ seqscan นั้นเร็วกว่า การบังคับenable_seqscan = offไม่ได้ใช้อย่างใดอย่างหนึ่งเนื่องจากการอ่านดัชนีช้าเท่ากับการสแกน seq ซึ่งอาจแย่กว่านั้น

แบบสอบถามต้องเรียกใช้การส่งคืนเป็นระยะสุดท้ายเสมอ

ใช้ Postgres 9.3

อธิบาย / วิเคราะห์ (มี 1.7 ล้านบันทึก):

set enable_seqscan=true;
explain analyze select max(created_at),equipment_id from geoposition_records group by equipment_id;
"HashAggregate  (cost=47803.77..47804.34 rows=57 width=12) (actual time=1935.536..1935.556 rows=58 loops=1)"
"  ->  Seq Scan on geoposition_records  (cost=0.00..39544.51 rows=1651851 width=12) (actual time=0.029..494.296 rows=1651851 loops=1)"
"Total runtime: 1935.632 ms"

set enable_seqscan=false;
explain analyze select max(created_at),equipment_id from geoposition_records group by equipment_id;
"GroupAggregate  (cost=0.00..2995933.57 rows=57 width=12) (actual time=222.034..11305.073 rows=58 loops=1)"
"  ->  Index Scan using geoposition_records_equipment_id_created_at_idx on geoposition_records  (cost=0.00..2987673.75 rows=1651851 width=12) (actual time=0.062..10248.703 rows=1651851 loops=1)"
"Total runtime: 11305.161 ms"

ครั้งล่าสุดที่ฉันตรวจสอบว่าไม่มีNULLค่าใด ๆในequipment_idเปอร์เซ็นต์ที่คาดว่าจะต่ำกว่า 0.1%
Feyd

คำตอบ:


10

ดัชนีต้นไม้หลายคอลัมน์ธรรมดาควรทำงานหลังจากทั้งหมด:

CREATE INDEX foo_idx
ON geoposition_records (equipment_id, created_at DESC NULLS LAST);

ทำไมDESC NULLS LAST?

ฟังก์ชัน

หากคุณไม่สามารถพูดถึงผู้วางแผนคิวรีได้ฟังก์ชั่นการวนลูปผ่านตารางอุปกรณ์ควรทำเคล็ดลับ การค้นหาอุปกรณ์หนึ่งครั้งในแต่ละครั้งจะใช้ดัชนี สำหรับจำนวนเล็กน้อย (57 การตัดสินจากEXPLAIN ANALYZEผลลัพธ์ของคุณ) มันเร็วมาก
จะปลอดภัยไหมถ้าคุณคิดว่าคุณมีequipmentโต๊ะ?

CREATE OR REPLACE FUNCTION f_latest_equip()
  RETURNS TABLE (equipment_id int, latest timestamp) AS
$func$
BEGIN
FOR equipment_id IN
   SELECT e.equipment_id FROM equipment e ORDER BY 1
LOOP
   SELECT g.created_at
   FROM   geoposition_records g
   WHERE  g.equipment_id = f_latest_equip.equipment_id
                           -- prepend function name to disambiguate
   ORDER  BY g.created_at DESC NULLS LAST
   LIMIT  1
   INTO   latest;

   RETURN NEXT;
END LOOP;
END  
$func$  LANGUAGE plpgsql STABLE;

ทำให้การโทรที่ดีเช่นกัน:

SELECT * FROM f_latest_equip();

แบบสอบถามย่อยที่สัมพันธ์กัน

ลองนึกถึงมันโดยใช้equipmentตารางนี้คุณสามารถทำงานสกปรกด้วยแบบสอบถามย่อยที่มีความสัมพันธ์ต่ำเพื่อผลที่ยอดเยี่ยม:

SELECT equipment_id
     ,(SELECT created_at
       FROM   geoposition_records
       WHERE  equipment_id = eq.equipment_id
       ORDER  BY created_at DESC NULLS LAST
       LIMIT  1) AS latest
FROM   equipment eq;

ประสิทธิภาพดีมาก

LATERAL เข้าร่วม Postgres 9.3+

SELECT eq.equipment_id, r.latest
FROM   equipment eq
LEFT   JOIN LATERAL (
   SELECT created_at
   FROM   geoposition_records
   WHERE  equipment_id = eq.equipment_id
   ORDER  BY created_at DESC NULLS LAST
   LIMIT  1
   ) r(latest) ON true;

คำอธิบายโดยละเอียด:

ประสิทธิภาพที่คล้ายกันกับแบบสอบถามย่อยที่สัมพันธ์กัน เปรียบเทียบประสิทธิภาพของmax(), DISTINCT ONฟังก์ชั่นที่มีลักษณะร่วมแบบสอบถามย่อยและLATERALในครั้งนี้:

SQL ซอ


1
@ErwinBrandstetter นี่คือสิ่งที่ฉันได้ลองหลังจากคำตอบจาก Colin แต่ฉันไม่สามารถหยุดคิดว่านี่เป็นวิธีแก้ปัญหาที่ใช้ชนิดของฐานข้อมูลด้าน n + 1 แบบสอบถาม (ไม่แน่ใจว่าตกอยู่ใน antipattern เพราะมี ไม่มีค่าใช้จ่ายในการเชื่อมต่อ) ... ฉันสงสัยว่าตอนนี้เหตุใดกลุ่มจึงมีอยู่ถ้ามันไม่สามารถจัดการบันทึกได้ไม่กี่ล้านรายการอย่างถูกต้อง ... มันไม่สมเหตุสมผลใช่ไหม? เป็นสิ่งที่เราขาดหายไป ในที่สุดคำถามก็เปลี่ยนไปเล็กน้อยและเรากำลังสมมติว่ามีโต๊ะอุปกรณ์ ... ฉันอยากจะรู้ว่าจริงๆแล้วมันมีวิธีอื่นอยู่ไหม
Feyd

3

พยายาม 1

ถ้า

  1. ฉันมีequipmentตารางแยกต่างหากและ
  2. ฉันมีดัชนีใน geoposition_records(equipment_id, created_at desc)

จากนั้นต่อไปนี้ใช้งานได้สำหรับฉัน:

select id as equipment_id, (select max(created_at)
                            from geoposition_records
                            where equipment_id = equipment.id
                           ) as max_created_at
from equipment;

ผมไม่สามารถที่จะบังคับ PG จะทำแบบสอบถามเพื่อตรวจสอบอย่างรวดเร็วทั้งรายชื่อของequipment_ids max(created_at)และที่เกี่ยวข้อง แต่ฉันจะลองอีกครั้งในวันพรุ่งนี้!

พยายาม 2

ฉันพบลิงค์นี้: http://zogovic.com/post/44856908222/optimizing-postgresql-query-for-distinct-values การรวมเทคนิคนี้เข้ากับคำถามของฉันตั้งแต่พยายาม 1 ฉันได้รับ:

WITH RECURSIVE equipment(id) AS (
    SELECT MIN(equipment_id) FROM geoposition_records
  UNION
    SELECT (
      SELECT equipment_id
      FROM geoposition_records
      WHERE equipment_id > equipment.id
      ORDER BY equipment_id
      LIMIT 1
    )
    FROM equipment WHERE id IS NOT NULL
)
SELECT id AS equipment_id, (SELECT MAX(created_at)
                            FROM geoposition_records
                            WHERE equipment_id = equipment.id
                           ) AS max_created_at
FROM equipment;

และนี่ใช้งานได้อย่างรวดเร็ว! แต่คุณต้องการ

  1. แบบฟอร์มสอบถามที่บิดเบี้ยวนี้และ
  2. geoposition_records(equipment_id, created_at desc)ดัชนีใน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.