การเพิ่มประสิทธิภาพการสืบค้นในช่วงเวลาที่ประทับ (สองคอลัมน์)


96

ฉันใช้ PostgreSQL 9.1 บน Ubuntu 12.04

ฉันต้องเลือกระเบียนภายในช่วงเวลาหนึ่ง: ตารางของฉันtime_limitsมีสองtimestampฟิลด์และหนึ่งintegerคุณสมบัติ มีคอลัมน์เพิ่มเติมในตารางจริงของฉันที่ไม่เกี่ยวข้องกับแบบสอบถามนี้

create table (
   start_date_time timestamp,
   end_date_time timestamp, 
   id_phi integer, 
   primary key(start_date_time, end_date_time,id_phi);

ตารางนี้มีเร็กคอร์ด 2M โดยประมาณ

ข้อความค้นหาต่อไปนี้ใช้เวลามหาศาล

select * from time_limits as t 
where t.id_phi=0 
and t.start_date_time <= timestamp'2010-08-08 00:00:00'
and t.end_date_time   >= timestamp'2010-08-08 00:05:00';

ดังนั้นฉันจึงพยายามเพิ่มดัชนีอื่น - ค่าผกผันของ PK:

create index idx_inversed on time_limits(id_phi, start_date_time, end_date_time);

ฉันได้รับความประทับใจที่ประสิทธิภาพการทำงานดีขึ้น: เวลาในการเข้าถึงระเบียนที่อยู่ตรงกลางของตารางดูเหมือนจะสมเหตุสมผลมากกว่า: อยู่ระหว่าง 40 ถึง 90 วินาที

แต่ก็ยังคงมีค่าหลายสิบวินาทีสำหรับช่วงกลางของช่วงเวลา และอีกสองครั้งเมื่อกำหนดเป้าหมายท้ายตาราง (พูดตามลำดับเหตุการณ์)

ฉันพยายามexplain analyzeเป็นครั้งแรกเพื่อรับแผนแบบสอบถามนี้:

 Bitmap Heap Scan on time_limits  (cost=4730.38..22465.32 rows=62682 width=36) (actual time=44.446..44.446 rows=0 loops=1)
   Recheck Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
   ->  Bitmap Index Scan on idx_time_limits_phi_start_end  (cost=0.00..4714.71 rows=62682 width=0) (actual time=44.437..44.437 rows=0 loops=1)
         Index Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
 Total runtime: 44.507 ms

ดูผลลัพธ์ที่ depesz.com

ฉันจะทำอย่างไรเพื่อปรับการค้นหาให้เหมาะสม ท่านสามารถเข้าดูตลอดเวลาที่ใช้การสแกนสองคอลัมน์ timestamps ครั้งหนึ่งเคยมีการตั้งค่าid_phi 0และฉันไม่เข้าใจการสแกนขนาดใหญ่ (แถว 60K!) ในการประทับเวลา พวกเขาไม่ได้จัดทำดัชนีโดยคีย์หลักและidx_inversedฉันเพิ่ม?

ฉันควรเปลี่ยนจากประเภทการประทับเวลาเป็นอย่างอื่นหรือไม่

ฉันได้อ่านนิดหน่อยเกี่ยวกับดัชนี GIST และ GIN ฉันรวบรวมพวกเขาจะมีประสิทธิภาพมากขึ้นในเงื่อนไขบางประการสำหรับประเภทที่กำหนดเอง มันเป็นตัวเลือกที่ทำงานได้สำหรับกรณีการใช้งานของฉัน?


1
ก็เป็น 45s ฉันไม่รู้ว่าทำไมมันถึงบอกว่า 45ms ฉันจะไม่เริ่มบ่นถ้ามันเร็วถึง 45ms ... :-) อาจจะเป็นข้อผิดพลาดในการอธิบายผลการวิเคราะห์ หรืออาจเป็นเวลาของการวิเคราะห์ที่จะดำเนินการ dunno แต่ 40/50 วินาทีเป็นสิ่งที่ฉันวัด
Stephane Rolland

2
เวลาที่มีการรายงานในexplain analyzeการส่งออกเป็นเวลาที่แบบสอบถามที่จำเป็นบนเซิร์ฟเวอร์ หากเคียวรีของคุณใช้เวลา 45 วินาทีเวลาเพิ่มเติมจะถูกใช้ในการถ่ายโอนข้อมูลจากฐานข้อมูลไปยังโปรแกรมที่รันคิวรีหลังจากทั้งหมดคือ 62682 แถวและหากแต่ละแถวมีขนาดใหญ่ (เช่นมีความยาวvarcharหรือtextคอลัมน์) สิ่งนี้อาจส่งผลต่อเวลาการถ่ายโอน ฮวบ
a_horse_with_no_name

@a_horse_with_no_name: rows=62682 rowsคือการวางแผนของประมาณการ แบบสอบถามส่งคืน 0 แถว (actual time=44.446..44.446 rows=0 loops=1)
Erwin Brandstetter

@ErwinBrandstetter: อ่าใช่ ฉันมองข้ามไปว่า แต่ถึงกระนั้นฉันยังไม่เคยเห็นผลลัพธ์ของการวิเคราะห์โกหกเกี่ยวกับเวลาดำเนินการ
a_horse_with_no_name

คำตอบ:


162

สำหรับ Postgres 9.1 หรือใหม่กว่า:

CREATE INDEX idx_time_limits_ts_inverse
ON time_limits (id_phi, start_date_time, end_date_time DESC);

ในกรณีส่วนใหญ่การเรียงลำดับของดัชนีนั้นแทบจะไม่เกี่ยวข้องกัน Postgres สามารถสแกนไปด้านหลังได้อย่างรวดเร็ว แต่สำหรับการสืบค้นที่หลากหลายในหลายคอลัมน์มันสามารถสร้างความแตกต่างอย่างมาก ที่เกี่ยวข้องอย่างใกล้ชิด:

พิจารณาคำถามของคุณ:

SELECT *
FROM   time_limits
WHERE  id_phi = 0
AND    start_date_time <= '2010-08-08 00:00'
AND    end_date_time   >= '2010-08-08 00:05';

ลำดับการเรียงของคอลัมน์แรกid_phiในดัชนีนั้นไม่เกี่ยวข้อง เนื่องจากมีการตรวจสอบความเท่าเทียมกัน ( =) จึงควรมาก่อน คุณพูดถูก เพิ่มเติมในคำตอบที่เกี่ยวข้องนี้:

Postgres สามารถข้ามไปได้id_phi = 0ในเวลาไม่นานและพิจารณาสองคอลัมน์ต่อไปนี้ของดัชนีที่ตรงกัน สิ่งเหล่านี้ถูกสอบถามด้วยเงื่อนไขช่วงของลำดับการเรียงกลับ ( <=, >=) ในดัชนีของฉันแถวที่ผ่านการคัดเลือกมาก่อน ควรเป็นวิธีที่เร็วที่สุดด้วยดัชนี B-Tree 1 :

  • คุณต้องการstart_date_time <= something: ดัชนีมีการประทับเวลาที่เร็วที่สุดก่อน
    • หากมีคุณสมบัติให้ตรวจสอบคอลัมน์ 3 เรียกคืน
      จนกว่าแถวแรกจะไม่ผ่านการรับรอง (เร็วสุด)
  • คุณต้องการend_date_time >= something: ดัชนีมีการประทับเวลาล่าสุดก่อน
    • หากมีคุณสมบัติให้ดึงข้อมูลแถวต่อไปจนกว่าแถวแรกจะไม่ (เร็วสุด)
      ดำเนินการต่อด้วยค่าถัดไปสำหรับคอลัมน์ 2 ..

Postgres สามารถสแกนไปข้างหน้าหรือข้างหลัง วิธีที่คุณมีดัชนีมันต้องอ่านแถวทั้งหมดที่ตรงกันในสองคอลัมน์แรกแล้วกรองในคอลัมน์ที่สาม อย่าลืมอ่านบทดัชนีและORDER BYในคู่มือ มันเหมาะกับคำถามของคุณค่อนข้างดี

จำนวนแถวเท่ากันในสองคอลัมน์แรก?
มีเพียงไม่กี่แห่งที่start_date_timeอยู่ใกล้กับจุดเริ่มต้นของช่วงเวลาของตาราง แต่เกือบทุกแถวid_phi = 0ที่ส่วนท้ายของตาราง! ดังนั้นประสิทธิภาพจะลดลงเมื่อเวลาเริ่มต้นในภายหลัง

ประมาณการณ์ของผู้วางแผน

ผู้วางแผนประมาณค่าrows=62682สำหรับแบบสอบถามตัวอย่างของคุณ ในบรรดานั้นไม่มีคุณสมบัติ ( rows=0) คุณอาจได้รับการประมาณการที่ดีขึ้นหากคุณเพิ่มเป้าหมายสถิติสำหรับตาราง สำหรับแถว 2.000.000 ...

ALTER TABLE time_limits ALTER start_date_time SET STATISTICS 1000;
ALTER TABLE time_limits ALTER end_date_time   SET STATISTICS 1000;

... อาจจ่ายเงิน หรือสูงกว่านั้น เพิ่มเติมในคำตอบที่เกี่ยวข้องนี้:

ฉันเดาว่าคุณไม่ต้องการสิ่งนั้นสำหรับid_phi(ค่าที่แตกต่างกันเพียงไม่กี่ค่าเท่านั้น, กระจายเท่า ๆ กัน), แต่สำหรับค่าประทับเวลา (ค่าที่แตกต่างจำนวนมาก, การกระจายแบบไม่สม่ำเสมอ)
ฉันไม่คิดว่ามันจะสำคัญอะไรกับดัชนีที่ได้รับการปรับปรุง

CLUSTER / pg_repack

หากคุณต้องการได้เร็วขึ้นคุณสามารถปรับปรุงลำดับของแถวในตารางได้ หากคุณสามารถล็อคตารางของคุณโดยเฉพาะในช่วงเวลาสั้น ๆ (เช่นในเวลาปิด) เพื่อเขียนตารางและแถวคำสั่งของคุณใหม่ตามดัชนี:

ALTER TABLE time_limits CLUSTER ON idx_time_limits_inversed;

ด้วยการเข้าถึงพร้อมกันให้พิจารณาpg_repackซึ่งสามารถทำได้โดยไม่ต้องล็อกแบบเอกสิทธิ์

ไม่ว่าจะด้วยวิธีใดเอฟเฟ็กต์คือบล็อกน้อยต้องอ่านจากตารางและทุกอย่างถูกจัดเรียงไว้ล่วงหน้า มันเป็นเอฟเฟกต์หนึ่งครั้งที่ทวีความรุนแรงขึ้นเมื่อเวลาผ่านไปด้วยการเขียนลงบนตารางที่เรียงลำดับฟิสิคัลการเรียงลำดับ

ดัชนี GiST ใน Postgres 9.2+

1ด้วย pg 9.2+ มีอีกทางเลือกหนึ่งอาจจะเร็วกว่า:ดัชนี GiST สำหรับคอลัมน์ช่วง

  • มีในตัวชนิดช่วงtimestampและtimestamp with time zone: ,tsrange tstzrangeดัชนี btree เป็นปกติได้เร็วขึ้นเพื่อเพิ่มคอลัมน์เช่นinteger id_phiขนาดเล็กและถูกกว่าเพื่อรักษาด้วย แต่แบบสอบถามอาจจะยังเร็วกว่าโดยรวมกับดัชนีที่รวมกัน

  • เปลี่ยนนิยามตารางของคุณหรือใช้ดัชนีการแสดงออก

  • สำหรับดัชนี GIST หลายคอลัมน์ที่อยู่ในมือคุณยังต้องโมดูลเพิ่มเติมbtree_gistติดตั้ง (หนึ่งครั้งต่อฐานข้อมูล) integerซึ่งมีชั้นเรียนผู้ประกอบการที่จะรวม

Trifecta! ดัชนีสรุปสาระสำคัญการทำงานแบบหลายคอลัมน์ :

CREATE EXTENSION IF NOT EXISTS btree_gist;  -- if not installed, yet

CREATE INDEX idx_time_limits_funky ON time_limits USING gist
(id_phi, tsrange(start_date_time, end_date_time, '[]'));

ใช้โอเปอเรเตอร์ "บรรจุพิสัย"@>ในแบบสอบถามของคุณทันที

SELECT *
FROM   time_limits
WHERE  id_phi = 0
AND    tsrange(start_date_time, end_date_time, '[]')
    @> tsrange('2010-08-08 00:00', '2010-08-08 00:05', '[]')

ดัชนี SP-GiST ใน Postgres 9.3+

SP-GISTดัชนีอาจจะได้เร็วยิ่งขึ้นสำหรับชนิดของแบบสอบถามนี้ - ยกเว้นว่าquoting คู่มือการใช้งาน :

ขณะนี้มีเพียงประเภทดัชนี B-tree, GiST, GIN และ BRIN เท่านั้นที่สนับสนุนดัชนีหลายคอลัมน์

ยังคงเป็นจริงใน Postgres 12.
คุณจะต้องในการรวมspgistดัชนีในเพียงแค่(tsrange(...))มีสองดัชนีbtree (id_phi)ด้วยค่าใช้จ่ายเพิ่มเติมฉันไม่แน่ใจว่าสิ่งนี้สามารถแข่งขันได้
คำตอบที่เกี่ยวข้องพร้อมกับเกณฑ์มาตรฐานสำหรับtsrangeคอลัมน์:


78
ฉันควรบอกสิ่งนี้อย่างน้อยครั้งเดียวว่าคำตอบของคุณใน SO และ DBA แต่ละรายการมีมูลค่าเพิ่ม / การแสดงออกที่สูงมากและส่วนใหญ่แล้วจะเสร็จสมบูรณ์ที่สุด เพียงพูดครั้งเดียว: เคารพ!
Stephane Rolland

1
Merci bien! :) ดังนั้นคุณได้รับผลลัพธ์ที่เร็วขึ้น?
เออร์วิน Brandstetter

ฉันต้องทำสำเนาขนาดใหญ่ให้เสร็จซึ่งสร้างจากเคียวรีที่น่าอึดอัดใจอย่างมากของฉันเพื่อให้กระบวนการทำงานช้าลงจริง ๆ มันก็เปลี่ยนไปหลายชั่วโมงก่อนที่ฉันจะถามคำถาม แต่ฉันได้คำนวนแล้วและฉันตัดสินใจที่จะปล่อยให้มันหมุนจนถึงเช้าวันพรุ่งนี้มันจะเสร็จแล้วและตารางใหม่พร้อมที่จะเติมเต็มพรุ่งนี้ ฉันพยายามสร้างดัชนีของคุณไปพร้อม ๆ กันในระหว่างงาน แต่เนื่องจากมีการเข้าถึงมากเกินไป (ฉันคิดว่า) การสร้างดัชนีควรถูกล็อค ฉันจะทำซ้ำเวลาทดสอบเดียวกันอีกครั้งในวันนี้ด้วยวิธีการแก้ปัญหาของคุณ ฉันได้ดูด้วยว่าการอัพเกรดเป็น 9.2 ;-) สำหรับเดเบียน / อูบุนตู
Stephane Rolland

2
@StephaneRolland: ยังคงน่าสนใจว่าทำไมการอธิบายผลการวิเคราะห์แสดงให้เห็น 45 ล้านวินาทีในขณะที่คุณเห็นแบบสอบถามใช้เวลานานกว่า 40 วินาที
a_horse_with_no_name

1
@John: Postgres สามารถสำรวจดัชนีไปข้างหน้าหรือข้างหลัง แต่ไม่สามารถเปลี่ยนทิศทางในการสแกนเดียวกันได้ ตามหลักแล้วคุณจะต้องมีแถวที่มีคุณสมบัติเหมาะสมทั้งหมดต่อโหนดก่อน (หรือสุดท้าย) แต่จะต้องมีการจัดตำแหน่งเดียวกัน
Erwin Brandstetter

5

คำตอบของ Erwin นั้นมีความครอบคลุมอยู่แล้วอย่างไรก็ตาม:

ประเภทช่วงเวลาสำหรับการประทับเวลามีอยู่ใน PostgreSQL 9.1 พร้อมส่วนขยายชั่วคราวจาก Jeff Davis: https://github.com/jeff-davis/PostgreSQL-Temporal

หมายเหตุ: มีคุณสมบัติที่ จำกัด (ใช้ Timestamptz และคุณสามารถมีรูปแบบ '[)' ทับซ้อน Afaik เท่านั้น) นอกจากนี้ยังมีเหตุผลดีๆมากมายที่ควรอัปเกรดเป็น PostgreSQL 9.2


3

คุณสามารถลองสร้างดัชนีหลายคอลัมน์ในลำดับอื่น:

primary key(id_phi, start_date_time,end_date_time);

ฉันโพสต์เมื่อคำถามที่คล้ายกันที่เกี่ยวข้องกับการจัดเรียงของดัชนีในดัชนีหลายคอลัมน์ กุญแจพยายามใช้เงื่อนไขที่เข้มงวดที่สุดก่อนเพื่อลดพื้นที่การค้นหา

แก้ไข : ความผิดพลาดของฉัน ตอนนี้ฉันเห็นว่าคุณได้กำหนดดัชนีนี้แล้ว


ฉันมีทั้งดัชนีแล้ว ยกเว้นคีย์หลักคืออีกตัวหนึ่ง แต่ดัชนีที่คุณเสนอมีอยู่แล้วและเป็นคีย์ที่ใช้ถ้าคุณดูคำอธิบาย:Bitmap Index Scan on idx_time_limits_phi_start_end
Stephane Rolland

1

ฉันจัดการเพื่อเพิ่มอย่างรวดเร็ว (จาก 1 วินาทีถึง 70 มิลลิวินาที)

ฉันมีตารางที่มีการรวมการวัดจำนวนมากและหลายระดับ ( lคอลัมน์) (30s, 1m, 1h, ฯลฯ ) มีคอลัมน์ที่มีขอบเขตสองช่วง: $sสำหรับการเริ่มต้นและ$eสิ้นสุด

ฉันสร้างดัชนีหลายคอลัมน์สองรายการ: หนึ่งดัชนีสำหรับการเริ่มต้นและอีกดัชนีสำหรับจุดสิ้นสุด

ฉันปรับคิวรีแบบเลือก: เลือกช่วงที่ขอบเขตเริ่มต้นของพวกเขาอยู่ในช่วงที่กำหนด เลือกช่วงเพิ่มเติมที่ขอบเขตสิ้นสุดของพวกเขาอยู่ในช่วงที่กำหนด

อธิบายแสดงให้เห็นถึงสองสายของแถวโดยใช้ดัชนีของเราอย่างมีประสิทธิภาพ

ดัชนี:

drop index if exists agg_search_a;
CREATE INDEX agg_search_a
ON agg (measurement_id, l, "$s");

drop index if exists agg_search_b;
CREATE INDEX agg_search_b
ON agg (measurement_id, l, "$e");

เลือกคำค้นหา:

select "$s", "$e", a, t, b, c from agg
where 
    measurement_id=0 
    and l =  '30s'
    and (
        (
            "$s" > '2013-05-01 02:05:05'
            and "$s" < '2013-05-01 02:18:15'
        )
        or 
        (
             "$e" > '2013-05-01 02:00:05'
            and "$e" < '2013-05-01 02:18:05'
        )
    )

;

อธิบาย:

[
  {
    "Execution Time": 0.058,
    "Planning Time": 0.112,
    "Plan": {
      "Startup Cost": 10.18,
      "Rows Removed by Index Recheck": 0,
      "Actual Rows": 37,
      "Plans": [
    {
      "Startup Cost": 10.18,
      "Actual Rows": 0,
      "Plans": [
        {
          "Startup Cost": 0,
          "Plan Width": 0,
          "Actual Rows": 26,
          "Node Type": "Bitmap Index Scan",
          "Index Cond": "((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$s\" > '2013-05-01 02:05:05'::timestamp without time zone) AND (\"$s\" < '2013-05-01 02:18:15'::timestamp without time zone))",
          "Plan Rows": 29,
          "Parallel Aware": false,
          "Actual Total Time": 0.016,
          "Parent Relationship": "Member",
          "Actual Startup Time": 0.016,
          "Total Cost": 5,
          "Actual Loops": 1,
          "Index Name": "agg_search_a"
        },
        {
          "Startup Cost": 0,
          "Plan Width": 0,
          "Actual Rows": 36,
          "Node Type": "Bitmap Index Scan",
          "Index Cond": "((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$e\" > '2013-05-01 02:00:05'::timestamp without time zone) AND (\"$e\" < '2013-05-01 02:18:05'::timestamp without time zone))",
          "Plan Rows": 39,
          "Parallel Aware": false,
          "Actual Total Time": 0.011,
          "Parent Relationship": "Member",
          "Actual Startup Time": 0.011,
          "Total Cost": 5.15,
          "Actual Loops": 1,
          "Index Name": "agg_search_b"
        }
      ],
      "Node Type": "BitmapOr",
      "Plan Rows": 68,
      "Parallel Aware": false,
      "Actual Total Time": 0.027,
      "Parent Relationship": "Outer",
      "Actual Startup Time": 0.027,
      "Plan Width": 0,
      "Actual Loops": 1,
      "Total Cost": 10.18
    }
      ],
      "Exact Heap Blocks": 1,
      "Node Type": "Bitmap Heap Scan",
      "Plan Rows": 68,
      "Relation Name": "agg",
      "Alias": "agg",
      "Parallel Aware": false,
      "Actual Total Time": 0.037,
      "Recheck Cond": "(((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$s\" > '2013-05-01 02:05:05'::timestamp without time zone) AND (\"$s\" < '2013-05-01 02:18:15'::timestamp without time zone)) OR ((measurement_id = 0) AND ((l)::text = '30s'::text) AND (\"$e\" > '2013-05-01 02:00:05'::timestamp without time zone) AND (\"$e\" < '2013-05-01 02:18:05'::timestamp without time zone)))",
      "Lossy Heap Blocks": 0,
      "Actual Startup Time": 0.033,
      "Plan Width": 44,
      "Actual Loops": 1,
      "Total Cost": 280.95
    },
    "Triggers": []
  }
]

เคล็ดลับคือโหนดแผนของคุณมีแถวที่ต้องการเท่านั้น ก่อนหน้านี้เรามีหลายพันแถวในโหนดแผนเพราะมันเลือกall points from some point in time to the very endจากนั้นโหนดถัดไปลบแถวที่ไม่จำเป็นออก

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