ปรับปรุงประสิทธิภาพของ COUNT / GROUP-BY ในตาราง PostgresSQL ขนาดใหญ่?


24

ฉันใช้ PostgresSQL 9.2 และมีความสัมพันธ์ 12 คอลัมน์มีประมาณ 6,700,000 แถว มันมีโหนดในพื้นที่ 3 มิติแต่ละคนอ้างอิงผู้ใช้ (ผู้สร้างมัน) ในการสอบถามผู้ใช้รายใดที่สร้างจำนวนโหนดที่ฉันทำต่อไปนี้ (เพิ่มexplain analyzeสำหรับข้อมูลเพิ่มเติม):

EXPLAIN ANALYZE SELECT user_id, count(user_id) FROM treenode WHERE project_id=1 GROUP BY user_id;
                                                    QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=253668.70..253669.07 rows=37 width=8) (actual time=1747.620..1747.623 rows=38 loops=1)
   ->  Seq Scan on treenode  (cost=0.00..220278.79 rows=6677983 width=8) (actual time=0.019..886.803 rows=6677983 loops=1)
         Filter: (project_id = 1)
 Total runtime: 1747.653 ms

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

คุณมีคำแนะนำอื่นหรือไม่?


เพื่อความสมบูรณ์นี่คือคำจำกัดความของตารางที่สมบูรณ์พร้อมด้วยดัชนีทั้งหมดของมัน (โดยไม่มีข้อ จำกัด foreign key การอ้างอิงและทริกเกอร์):

    Column     |           Type           |                      Modifiers                    
---------------+--------------------------+------------------------------------------------------
 id            | bigint                   | not null default nextval('concept_id_seq'::regclass)
 user_id       | bigint                   | not null
 creation_time | timestamp with time zone | not null default now()
 edition_time  | timestamp with time zone | not null default now()
 project_id    | bigint                   | not null
 location      | double3d                 | not null
 reviewer_id   | integer                  | not null default (-1)
 review_time   | timestamp with time zone |
 editor_id     | integer                  |
 parent_id     | bigint                   |
 radius        | double precision         | not null default 0
 confidence    | integer                  | not null default 5
 skeleton_id   | bigint                   |
Indexes:
    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE CONSTRAINT, btree (id)
    "skeleton_id_treenode_index" btree (skeleton_id)
    "treenode_editor_index" btree (editor_id)
    "treenode_location_x_index" btree (((location).x))
    "treenode_location_y_index" btree (((location).y))
    "treenode_location_z_index" btree (((location).z))
    "treenode_parent_id" btree (parent_id)
    "treenode_user_index" btree (user_id)

แก้ไข:นี่คือผลลัพธ์เมื่อฉันใช้คิวรี (และดัชนี) ที่เสนอโดย @ypercube (เคียวรีใช้เวลาประมาณ 5.3 วินาทีโดยไม่มีEXPLAIN ANALYZE):

EXPLAIN ANALYZE SELECT u.id, ( SELECT COUNT(*) FROM treenode AS t WHERE t.project_id=1 AND t.user_id = u.id ) AS number_of_nodes FROM auth_user As u;
                                                                        QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on auth_user u  (cost=0.00..6987937.85 rows=46 width=4) (actual time=29.934..5556.147 rows=46 loops=1)
   SubPlan 1
     ->  Aggregate  (cost=151911.65..151911.66 rows=1 width=0) (actual time=120.780..120.780 rows=1 loops=46)
           ->  Bitmap Heap Scan on treenode t  (cost=4634.41..151460.44 rows=180486 width=0) (actual time=13.785..114.021 rows=145174 loops=46)
                 Recheck Cond: ((project_id = 1) AND (user_id = u.id))
                 Rows Removed by Index Recheck: 461076
                 ->  Bitmap Index Scan on treenode_user_index  (cost=0.00..4589.29 rows=180486 width=0) (actual time=13.082..13.082 rows=145174 loops=46)
                       Index Cond: ((project_id = 1) AND (user_id = u.id))
 Total runtime: 5556.190 ms
(9 rows)

Time: 5556.804 ms

แก้ไข 2:นี่คือผลลัพธ์เมื่อฉันใช้การindexเปิดproject_id, user_id(แต่ยังไม่มีการเพิ่มประสิทธิภาพของ schema) ตามที่ @ erwin-brandstetter แนะนำ (แบบสอบถามทำงานด้วย 1.5 วินาทีที่ความเร็วเดียวกับแบบสอบถามดั้งเดิมของฉัน):

EXPLAIN ANALYZE SELECT user_id, count(user_id) as ct FROM treenode WHERE project_id=1 GROUP BY user_id;
                                                        QUERY PLAN                                                      
---------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=253670.88..253671.24 rows=37 width=8) (actual time=1807.334..1807.339 rows=38 loops=1)
   ->  Seq Scan on treenode  (cost=0.00..220280.62 rows=6678050 width=8) (actual time=0.183..893.491 rows=6678050 loops=1)
         Filter: (project_id = 1)
 Total runtime: 1807.368 ms
(4 rows)

คุณมีตารางที่Usersใช้user_idเป็นคีย์หลักหรือไม่
ypercubeᵀᴹ

ฉันเพิ่งเห็นว่ามีส่วนเสริมคอลัมน์ร้านค้าของบุคคลที่สามสำหรับ Postgres นอกจากนี้ฉันแค่ต้องการโพสต์จากแอป iOS ใหม่
swasheck

2
ขอบคุณข้อมูลดีดีที่ชัดเจนคำถามที่สมบูรณ์ - รุ่นนิยามตาราง ฯลฯ
เครก Ringer

@ypercube ใช่ฉันมีตารางผู้ใช้แล้ว
tomka

หลายวิธีที่แตกต่างกันproject_idและuser_id? มีการอัปเดตตารางอย่างต่อเนื่องหรือคุณสามารถทำงานกับมุมมองที่ปรากฏ (บางครั้ง) ได้หรือไม่
Erwin Brandstetter

คำตอบ:


25

ปัญหาหลักคือดัชนีที่ขาดหายไป แต่มีมากกว่านั้น

SELECT user_id, count(*) AS ct
FROM   treenode
WHERE  project_id = 1
GROUP  BY user_id;
  • คุณมีหลายbigintคอลัมน์ อาจ overkill โดยปกติintegerเป็นมากกว่าเพียงพอสำหรับคอลัมน์ที่ชอบและproject_id user_idสิ่งนี้จะช่วยให้รายการถัดไป
    ในขณะที่การเพิ่มประสิทธิภาพความคมชัดโต๊ะพิจารณาคำตอบที่เกี่ยวข้องนี้ให้ความสำคัญกับการจัดเรียงข้อมูลและpadding แต่ส่วนที่เหลือใช้กับ:

  • ช้างในห้อง : ไม่มีดัชนีใน project_idสร้างหนึ่ง นี่สำคัญกว่าคำตอบที่เหลือ
    ในขณะที่อยู่ในนั้นให้ทำดัชนีดัชนีหลายคอลัมน์:

    CREATE INDEX treenode_project_id_user_id_index ON treenode (project_id, user_id);

    หากคุณทำตามคำแนะนำของฉันintegerจะสมบูรณ์แบบที่นี่:

  • user_idถูกกำหนดNOT NULLดังนั้นจึงcount(user_id)เทียบเท่ากับcount(*)แต่หลังสั้นกว่าและเร็วกว่าเล็กน้อย (ในแบบสอบถามเฉพาะนี้สิ่งนี้จะใช้แม้จะไม่user_idถูกกำหนดNOT NULL)

  • idที่มีอยู่แล้วคีย์หลักเพิ่มเติมUNIQUEข้อ จำกัด คือบัลลาสต์ไร้ประโยชน์ วางมันลง:

    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE CONSTRAINT, btree (id)

    นอกเหนือ: ฉันจะไม่ใช้idเป็นชื่อคอลัมน์ treenode_idใช้สิ่งที่สื่อความหมายเช่น

เพิ่มข้อมูลแล้ว

Q: A:How many different project_id and user_id?
not more than five different project_id

นั่นหมายความว่า Postgres จะต้องอ่านประมาณ 20% ของตารางทั้งหมดเพื่อให้ตรงกับคำค้นหาของคุณ หากไม่สามารถใช้การสแกนเฉพาะดัชนีการสแกนตามลำดับบนโต๊ะจะเร็วกว่าการทำดัชนีใด ๆ ไม่มีประสิทธิภาพมากขึ้นที่จะได้รับที่นี่ - ยกเว้นโดยปรับการตั้งค่าตารางและเซิร์ฟเวอร์

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

เช่นเดียวกับหน้าคู่มือเพิ่มด้วย Postgres 9.6และPostgres วิกิพีเดียในการสแกนดัชนีเท่านั้น


1
เออร์วินขอบคุณสำหรับคำแนะนำของคุณ คุณถูกต้องuser_idและproject_id integerควรจะมากเกินพอ การใช้count(*)แทนที่จะcount(user_id)บันทึกที่นี่ประมาณ 70ms เป็นเรื่องดีที่รู้ ฉันได้เพิ่มการEXPLAIN ANALYZEสืบค้นหลังจากที่ฉันได้เพิ่มข้อเสนอแนะของคุณindexไปยังโพสต์แรก มันไม่ได้ปรับปรุงประสิทธิภาพ แต่ (แต่ก็ไม่เจ็บ) ดูเหมือนว่าindexจะไม่ได้ใช้เลย ฉันจะทดสอบการเพิ่มประสิทธิภาพของสคีมาในไม่ช้า
tomka

1
ถ้าฉันปิดการใช้งานseqscanดัชนีจะใช้ ( Index Only Scan using treenode_project_id_user_id_index on treenode) แต่แบบสอบถามใช้เวลาประมาณ 2.5 วินาทีจากนั้น (ซึ่งจะยาวกว่า seqscan ประมาณ 1 วินาที)
tomka

1
ขอบคุณสำหรับการอัปเดตของคุณ บิตที่หายไปเหล่านี้ควรเป็นส่วนหนึ่งของคำถามของฉันนั่นถูกต้อง ฉันไม่ได้ตระหนักถึงผลกระทบของพวกเขา ฉันจะเพิ่มประสิทธิภาพสคีมาของฉันตามที่คุณแนะนำ --- ลองดูสิ่งที่ฉันจะได้รับจากสิ่งนั้น ขอบคุณสำหรับคำอธิบายของคุณมันสมเหตุสมผลสำหรับฉันดังนั้นฉันจะทำเครื่องหมายคำตอบของคุณว่าเป็นคำตอบที่ได้รับการยอมรับ
tomka

7

ฉันจะเพิ่มดัชนีใน(project_id, user_id)และจากนั้นในรุ่น 9.3 ลองค้นหานี้:

SELECT u.user_id, c.number_of_nodes 
FROM users AS u
   , LATERAL
     ( SELECT COUNT(*) AS number_of_nodes 
       FROM treenode AS t
       WHERE t.project_id = 1 
         AND t.user_id = u.user_id
     ) c 
-- WHERE c.number_of_nodes > 0 ;   -- you probably want this as well
                                   -- to show only relevant users

ใน 9.2 ลองอันนี้:

SELECT u.user_id, 
       ( SELECT COUNT(*) 
         FROM treenode AS t
         WHERE t.project_id = 1 
           AND t.user_id = u.user_id
       ) AS number_of_nodes  
FROM users AS u ;

ฉันคิดว่าคุณมีusersโต๊ะ ถ้าไม่ใช่ให้แทนที่usersด้วย:
(SELECT DISTINCT user_id FROM treenode)


ขอบคุณมากสำหรับคำตอบของคุณ. คุณถูกต้องฉันมีตารางผู้ใช้ อย่างไรก็ตามการใช้แบบสอบถามของคุณใน 9.2 จะใช้เวลาประมาณ 5 วินาทีในการรับผลลัพธ์ - ไม่ว่าดัชนีจะถูกสร้างขึ้นหรือไม่ก็ตาม ฉันสร้างดัชนีเช่นนี้: CREATE INDEX treenode_user_index ON treenode USING btree (project_id, user_id);แต่ฉันก็ลองได้โดยไม่มีUSINGอนุประโยค ฉันพลาดอะไรไปหรือเปล่า?
tomka

มีกี่แถวในusersตารางและจำนวนแถวที่แบบสอบถามส่งกลับ (มีผู้ใช้ที่มีจำนวนproject_id=1เท่าใด) คุณสามารถแสดงคำอธิบายของแบบสอบถามนี้หลังจากที่คุณเพิ่มดัชนีได้หรือไม่
ypercubeᵀᴹ

1
ครั้งแรกฉันผิดในความคิดเห็นแรกของฉัน หากไม่มีดัชนีที่แนะนำของคุณจะใช้เวลาประมาณ 40 วินาที (!) เพื่อรับผลลัพธ์ มันใช้เวลาประมาณ 5 indexวินาทีพร้อมกับในสถานที่ ขอโทษสำหรับความสับสน. ในusersตารางของฉันฉันมี 46 รายการ แบบสอบถามส่งคืนเพียง 9 แถว น่าแปลกที่SELECT DISTINCT user_id FROM treenode WHERE project_id=1;ผลตอบแทน 38 แถว ฉันได้เพิ่มexplainไปยังโพสต์แรกของฉัน และเพื่อป้องกันความสับสนของฉันตารางเรียกว่าจริงusers auth_user
tomka

ฉันสงสัยว่าจะSELECT DISTINCT user_id FROM treenode WHERE project_id=1;ส่งคืน 38 แถวได้อย่างไรในขณะที่คิวรีส่งคืนเฉพาะ 9 บัฟเฟอร์
ypercubeᵀᴹ

ลองดูได้ไหม:SET enable_seqscan = OFF; (Query); SET enable_seqscan = ON;
ypercubeᵀᴹ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.