Postgres ทำการสแกนตามลำดับแทนการสแกนดัชนี


9

ฉันมีตารางที่มีประมาณ 10 ล้านแถวในนั้นและดัชนีในเขตข้อมูลวันที่ เมื่อฉันลองและแยกค่าที่ไม่ซ้ำกันของเขตข้อมูลที่จัดทำดัชนี Postgres เรียกใช้การสแกนตามลำดับแม้ว่าชุดผลลัพธ์มีเพียง 26 รายการ เหตุใดเครื่องมือเพิ่มประสิทธิภาพจึงเลือกแผนนี้ และฉันจะหลีกเลี่ยงได้อย่างไร

จากคำตอบอื่น ๆ ฉันสงสัยว่านี่เกี่ยวข้องกับแบบสอบถามมากเท่ากับดัชนี

explain select "labelDate" from pages group by "labelDate";
                              QUERY PLAN
-----------------------------------------------------------------------
 HashAggregate  (cost=524616.78..524617.04 rows=26 width=4)
   Group Key: "labelDate"
   ->  Seq Scan on pages  (cost=0.00..499082.42 rows=10213742 width=4)
(3 rows)

โครงสร้างตาราง:

http=# \d pages
                                       Table "public.pages"
     Column      |          Type          |        Modifiers
-----------------+------------------------+----------------------------------
 pageid          | integer                | not null default nextval('...
 createDate      | integer                | not null
 archive         | character varying(16)  | not null
 label           | character varying(32)  | not null
 wptid           | character varying(64)  | not null
 wptrun          | integer                | not null
 url             | text                   |
 urlShort        | character varying(255) |
 startedDateTime | integer                |
 renderStart     | integer                |
 onContentLoaded | integer                |
 onLoad          | integer                |
 PageSpeed       | integer                |
 rank            | integer                |
 reqTotal        | integer                | not null
 reqHTML         | integer                | not null
 reqJS           | integer                | not null
 reqCSS          | integer                | not null
 reqImg          | integer                | not null
 reqFlash        | integer                | not null
 reqJSON         | integer                | not null
 reqOther        | integer                | not null
 bytesTotal      | integer                | not null
 bytesHTML       | integer                | not null
 bytesJS         | integer                | not null
 bytesCSS        | integer                | not null
 bytesHTML       | integer                | not null
 bytesJS         | integer                | not null
 bytesCSS        | integer                | not null
 bytesImg        | integer                | not null
 bytesFlash      | integer                | not null
 bytesJSON       | integer                | not null
 bytesOther      | integer                | not null
 numDomains      | integer                | not null
 labelDate       | date                   |
 TTFB            | integer                |
 reqGIF          | smallint               | not null
 reqJPG          | smallint               | not null
 reqPNG          | smallint               | not null
 reqFont         | smallint               | not null
 bytesGIF        | integer                | not null
 bytesJPG        | integer                | not null
 bytesPNG        | integer                | not null
 bytesFont       | integer                | not null
 maxageMore      | smallint               | not null
 maxage365       | smallint               | not null
 maxage30        | smallint               | not null
 maxage1         | smallint               | not null
 maxage0         | smallint               | not null
 maxageNull      | smallint               | not null
 numDomElements  | integer                | not null
 numCompressed   | smallint               | not null
 numHTTPS        | smallint               | not null
 numGlibs        | smallint               | not null
 numErrors       | smallint               | not null
 numRedirects    | smallint               | not null
 maxDomainReqs   | smallint               | not null
 bytesHTMLDoc    | integer                | not null
 maxage365       | smallint               | not null
 maxage30        | smallint               | not null
 maxage1         | smallint               | not null
 maxage0         | smallint               | not null
 maxageNull      | smallint               | not null
 numDomElements  | integer                | not null
 numCompressed   | smallint               | not null
 numHTTPS        | smallint               | not null
 numGlibs        | smallint               | not null
 numErrors       | smallint               | not null
 numRedirects    | smallint               | not null
 maxDomainReqs   | smallint               | not null
 bytesHTMLDoc    | integer                | not null
 fullyLoaded     | integer                |
 cdn             | character varying(64)  |
 SpeedIndex      | integer                |
 visualComplete  | integer                |
 gzipTotal       | integer                | not null
 gzipSavings     | integer                | not null
 siteid          | numeric                |
Indexes:
    "pages_pkey" PRIMARY KEY, btree (pageid)
    "pages_date_url" UNIQUE CONSTRAINT, btree ("urlShort", "labelDate")
    "idx_pages_cdn" btree (cdn)
    "idx_pages_labeldate" btree ("labelDate") CLUSTER
    "idx_pages_urlshort" btree ("urlShort")
Triggers:
    pages_label_date BEFORE INSERT OR UPDATE ON pages
      FOR EACH ROW EXECUTE PROCEDURE fix_label_date()

คำตอบ:


8

นี่เป็นปัญหาที่ทราบกันดีเกี่ยวกับการเพิ่มประสิทธิภาพของ Postgres หากค่าที่แตกต่างกันอยู่ไม่กี่คน - เช่นเดียวกับในกรณีของคุณ - และคุณอยู่ใน 8.4+ รุ่นที่รวดเร็วมากแก้ปัญหาโดยใช้แบบสอบถาม recursive อธิบายไว้ที่นี่: Loose Indexscan

ข้อความค้นหาของคุณสามารถเขียนใหม่ได้ ( LATERALความต้องการเวอร์ชั่น 9.3 ขึ้นไป):

WITH RECURSIVE pa AS 
( ( SELECT labelDate FROM pages ORDER BY labelDate LIMIT 1 ) 
  UNION ALL
    SELECT n.labelDate 
    FROM pa AS p
         , LATERAL 
              ( SELECT labelDate 
                FROM pages 
                WHERE labelDate > p.labelDate 
                ORDER BY labelDate 
                LIMIT 1
              ) AS n
) 
SELECT labelDate 
FROM pa ;

Erwin Brandstetter มีคำอธิบายอย่างละเอียดและความหลากหลายของการสืบค้นในคำตอบนี้ (ในประเด็นที่เกี่ยวข้อง แต่แตกต่างกัน): เพิ่มประสิทธิภาพการค้นหากลุ่มตามแบบสอบถามเพื่อดึงข้อมูลบันทึกล่าสุดต่อผู้ใช้


6

แบบสอบถามที่ดีที่สุดมากขึ้นอยู่กับการกระจายข้อมูล

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

ไม่จำเป็นต้องมีส่วนร่วมpageid เลย (เหมือนที่คุณแสดงความคิดเห็น)

ดัชนี

ทั้งหมดที่คุณต้องเป็นดัชนีที่เรียบง่าย btree "labelDate"บน
ด้วยค่า NULL มากกว่าสองสามค่าในคอลัมน์ดัชนีบางส่วนช่วยได้มากขึ้น (และเล็กกว่า):

CREATE INDEX pages_labeldate_nonull_idx ON big ("labelDate")
WHERE  "labelDate" IS NOT NULL;

คุณชี้แจงภายหลัง:

0% NULL แต่หลังจากแก้ไขสิ่งต่าง ๆ เมื่อนำเข้าเท่านั้น

ดัชนีบางส่วนอาจจะยังคงให้ความรู้สึกในการออกกฎรัฐตัวกลางของแถวที่มีค่าเป็นศูนย์ จะหลีกเลี่ยงการอัพเดทดัชนีโดยไม่จำเป็น

สอบถาม

ขึ้นอยู่กับช่วงชั่วคราว

หากวันที่ของคุณปรากฏในช่วงต่อเนื่องโดยมีช่องว่างไม่มากเกินไปเราสามารถใช้ประเภทของข้อมูลdateเพื่อประโยชน์ของเรา มีเพียงจำนวน จำกัด ค่านับระหว่างสองค่าที่กำหนด หากช่องว่างมีน้อยสิ่งนี้จะเร็วที่สุด:

SELECT d."labelDate"
FROM  (
   SELECT generate_series(min("labelDate")::timestamp
                        , max("labelDate")::timestamp
                        , interval '1 day')::date AS "labelDate"
   FROM   pages
   ) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

ทำไมนักแสดงถึงต้องtimestampเข้าร่วมgenerate_series()? ดู:

ต่ำสุดและสูงสุดสามารถเลือกได้จากดัชนีราคาถูก หากคุณทราบวันที่ต่ำสุดและ / หรือสูงสุดที่เป็นไปได้จะมีราคาถูกลงเล็กน้อย ตัวอย่าง:

SELECT d."labelDate"
FROM  (SELECT date '2011-01-01' + g AS "labelDate"
       FROM   generate_series(0, now()::date - date '2011-01-01' - 1) g) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

หรือสำหรับช่วงเวลาที่ไม่เปลี่ยนรูป:

SELECT d."labelDate"
FROM  (SELECT date '2011-01-01' + g AS "labelDate"
       FROM generate_series(0, 363) g) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

สแกนดัชนีแบบหลวม

สิ่งนี้ทำงานได้ดีมากเมื่อมีการกระจายวันที่ใด ๆ (ตราบใดที่เรามีหลายแถวต่อวัน) โดยทั่วไปสิ่งที่@ypercube ให้อยู่แล้ว แต่มีบางจุดที่ดีและเราต้องแน่ใจว่าดัชนีที่เราชื่นชอบสามารถใช้ได้ทุกที่

WITH RECURSIVE p AS (
   ( -- parentheses required for LIMIT
   SELECT "labelDate"
   FROM   pages
   WHERE  "labelDate" IS NOT NULL
   ORDER  BY "labelDate"
   LIMIT  1
   ) 
   UNION ALL
   SELECT (SELECT "labelDate" 
           FROM   pages 
           WHERE  "labelDate" > p."labelDate" 
           ORDER  BY "labelDate" 
           LIMIT  1)
   FROM   p
   WHERE  "labelDate" IS NOT NULL
   ) 
SELECT "labelDate" 
FROM   p
WHERE  "labelDate" IS NOT NULL;
  • CTE แรกpนั้นมีประสิทธิภาพเหมือนกับ

    SELECT min("labelDate") FROM pages

    แต่รูปแบบ verbose ทำให้แน่ใจว่ามีการใช้ดัชนีบางส่วนของเรา นอกจากนี้แบบฟอร์มนี้มักจะเร็วขึ้นเล็กน้อยในประสบการณ์ของฉัน (และในการทดสอบของฉัน)

  • สำหรับคอลัมน์เดียวเท่านั้นเคียวรีย่อยที่สัมพันธ์กันในคำซ้ำของ rCTE ควรเร็วขึ้นเล็กน้อย สิ่งนี้ต้องแยกแถวที่ทำให้เกิด NULL สำหรับ "labelDate" ดู:

  • เพิ่มประสิทธิภาพ GROUP BY query เพื่อดึงข้อมูลบันทึกล่าสุดต่อผู้ใช้

มานี

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


-2

จากเอกสาร postgresql:

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

ดัชนีของคุณบน labelDate คือ btree ..

อ้างอิง:

http://www.postgresql.org/docs/9.1/static/sql-cluster.html


แม้จะมีเงื่อนไขเช่น "WHERE" labelDate "BETWEEN '2000-01-01' และ '2020-01-01' ยังคงเกี่ยวข้องกับการสแกนตามลำดับ
Charlie Clark

การทำคลัสเตอร์ในขณะนี้ (แม้ว่าข้อมูลจะถูกป้อนตามลำดับอย่างคร่าว ๆ ) แต่ก็ยังไม่ได้อธิบายการตัดสินใจของนักวางแผนคิวรีที่จะไม่ใช้ดัชนีแม้ว่าจะมีประโยค WHERE อยู่
Charlie Clark

คุณได้ลองปิดการใช้งานการสแกนตามลำดับสำหรับเซสชันหรือไม่ set enable_seqscan=offไม่ว่าในกรณีใดเอกสารมีความชัดเจน หากคุณทำคลัสเตอร์จะทำการสแกนตามลำดับ
Fabrizio Mazzoni

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