postgresql COUNT (DISTINCT …) ช้ามาก


166

ฉันมีแบบสอบถาม SQL ที่ง่ายมาก:

SELECT COUNT(DISTINCT x) FROM table;

ตารางของฉันมีประมาณ 1.5 ล้านแถว แบบสอบถามนี้ทำงานค่อนข้างช้า ใช้เวลาประมาณ 7.5 วินาทีเมื่อเทียบกับ

 SELECT COUNT(x) FROM table;

ซึ่งใช้เวลาประมาณ 435ms มีวิธีใดที่จะเปลี่ยนข้อความค้นหาของฉันเพื่อปรับปรุงประสิทธิภาพหรือไม่ ฉันได้ลองจัดกลุ่มและทำการนับเป็นประจำเช่นเดียวกับการวางดัชนีบน x; ทั้งคู่มีเวลาดำเนินการ 7.5 วินาทีเหมือนกัน


ฉันไม่คิดอย่างนั้น การรับค่าที่แตกต่างกัน 1.5 ล้านแถวกำลังจะช้า
Ry-

5
ฉันเพิ่งลองใน C # รับค่าแตกต่างของจำนวนเต็ม 1.5 ล้านจากหน่วยความจำใช้เวลากว่าหนึ่งวินาทีในคอมพิวเตอร์ของฉัน ดังนั้นฉันคิดว่าคุณคงโชคไม่ดี
Ry-

แผนแบบสอบถามจะขึ้นอยู่กับโครงสร้างของตาราง (ดัชนี) และการตั้งค่าของการปรับค่าคงที่ mem (งาน) mem, effective_cache_size, random_page_cost ด้วยการปรับที่เหมาะสมแบบสอบถามอาจถูกดำเนินการในเวลาน้อยกว่าหนึ่งวินาที
wildplasser

คุณเจาะจงมากกว่านี้ไหม? ดัชนีและการปรับค่าคงที่ใดจะต้องได้รับภายในไม่กี่วินาที? เพื่อความง่ายสมมติว่านี่คือตารางสองคอลัมน์ที่มีคีย์หลักในคอลัมน์แรก y และฉันกำลังทำแบบสอบถามนี้ 'แตกต่าง' ในคอลัมน์ที่สอง x ชนิด int มี 1.5 ล้านแถว
ferson2020

1
โปรดรวมคำจำกัดความของตารางด้วยดัชนีทั้งหมด ( \dผลลัพธ์ของpsqlดีมาก) และแม่นยำคอลัมน์ที่คุณมีปัญหา มันจะเป็นการดีถ้าได้เห็นEXPLAIN ANALYZEข้อความค้นหาทั้งสอง
vyegorov

คำตอบ:


316

คุณสามารถใช้สิ่งนี้:

SELECT COUNT(*) FROM (SELECT DISTINCT column_name FROM table_name) AS temp;

มันเร็วกว่า:

COUNT(DISTINCT column_name)

38
แบทแมนสอบถามศักดิ์สิทธิ์! สิ่งนี้เร่งความเร็ว postgres ของฉันนับตั้งแต่ 190 ถึง 4.5 ว้าว!
rogerdpack

20
ผมพบว่ากระทู้นี้บนwww.postgresql.orgซึ่งกล่าวถึงสิ่งเดียวกัน: การเชื่อมโยง หนึ่งในคำตอบ (โดย Jeff Janes) บอกว่า COUNT (DISTINCT ()) จัดเรียงตารางเพื่อทำงานแทนการใช้แฮช
Ankur

5
@Ankur ฉันขอถามคุณได้ไหม เนื่องจากCOUNT(DISTINCT())การเรียงลำดับจะเป็นประโยชน์อย่างแน่นอนหากมีดัชนีcolumn_nameโดยเฉพาะอย่างยิ่งที่มีจำนวนค่อนข้างน้อยwork_mem(ซึ่งการแฮชจะทำให้เกิดชุดจำนวนมาก) ตั้งแต่นั้นมาก็ไม่เลวเสมอไปที่จะใช้ COUNT (DISTINCT () _, ไม่ใช่เหรอ
St.Antario

2
@musmahn Count(column)จะนับเฉพาะค่าที่ไม่ใช่นัลเท่านั้น count(*)นับแถว ดังนั้นอันแรก / อันที่ยาวกว่าจะนับแถว null (หนึ่งครั้ง) เปลี่ยนเป็นcount(column_name)ทำให้พวกเขามีพฤติกรรมเหมือนกัน
GolezTrol

1
@ankur นี้ไม่ได้มีประโยชน์มากสำหรับฉัน. ไม่ได้รับการปรับปรุงใด ๆ ที่น่าทึ่ง
Shiwangini

11
-- My default settings (this is basically a single-session machine, so work_mem is pretty high)
SET effective_cache_size='2048MB';
SET work_mem='16MB';

\echo original
EXPLAIN ANALYZE
SELECT
        COUNT (distinct val) as aantal
FROM one
        ;

\echo group by+count(*)
EXPLAIN ANALYZE
SELECT
        distinct val
       -- , COUNT(*)
FROM one
GROUP BY val;

\echo with CTE
EXPLAIN ANALYZE
WITH agg AS (
    SELECT distinct val
    FROM one
    GROUP BY val
    )
SELECT COUNT (*) as aantal
FROM agg
        ;

ผล:

original                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36448.06..36448.07 rows=1 width=4) (actual time=1766.472..1766.472 rows=1 loops=1)
   ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=31.371..185.914 rows=1499845 loops=1)
 Total runtime: 1766.642 ms
(3 rows)

group by+count(*)
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=412.470..412.598 rows=1300 loops=1)
   ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=412.066..412.203 rows=1300 loops=1)
         ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=26.134..166.846 rows=1499845 loops=1)
 Total runtime: 412.686 ms
(4 rows)

with CTE
                                                             QUERY PLAN                                                             
------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36506.56..36506.57 rows=1 width=0) (actual time=408.239..408.239 rows=1 loops=1)
   CTE agg
     ->  HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=407.704..407.847 rows=1300 loops=1)
           ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=407.320..407.467 rows=1300 loops=1)
                 ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=24.321..165.256 rows=1499845 loops=1)
       ->  CTE Scan on agg  (cost=0.00..26.00 rows=1300 width=0) (actual time=407.707..408.154 rows=1300 loops=1)
     Total runtime: 408.300 ms
    (7 rows)

แผนเดียวกันกับ CTE อาจถูกผลิตโดยวิธีอื่น (ฟังก์ชั่นหน้าต่าง)


2
คุณพิจารณาถึงผลของการแคชแล้วหรือยัง? หากทำสาม "อธิบายวิเคราะห์" ในภายหลังคนแรกอาจดึงสิ่งต่าง ๆ จากดิสก์ในขณะที่ทั้งสองหลังอาจดึงข้อมูลจากหน่วยความจำอย่างรวดเร็ว
tobixen

แท้จริงแล้ว: effective_cache_size เป็นการตั้งค่าแรกเพื่อปรับแต่ง ของฉันคือ 2GB, IIRC
wildplasser

ฉันตั้ง effective_cache_size เป็น 2GB โดยไม่มีการเปลี่ยนแปลงประสิทธิภาพ การตั้งค่าอื่นใดที่คุณแนะนำให้ปรับแต่ง ถ้าเป็นเช่นนั้นเพื่ออะไร
ferson2020

1) วิธีการที่คุณไม่ได้ตั้งค่ามันได้หรือไม่ (คุณ HUP มันได้หรือไม่) 2) คุณมีหน่วยความจำที่มีอยู่จริงหรือไม่? 3) แสดงแผนของคุณให้เราทราบ 4) เครื่องของฉันอาจเร็วกว่านี้หรือคุณมีภาระมากขึ้นในการจัดการ @ ferson2020: Ok
wildplasser

ฉันตั้งค่ามันด้วยคำสั่ง: SET effective_cache_size = '2GB'; ฉันมีหน่วยความจำที่มีอยู่มากมาย ฉันลองใส่แผนการสืบค้นของฉันแล้ว แต่มันไม่พอดีในช่องแสดงความคิดเห็น
ferson2020

2

หากคุณcount(distinct(x))ช้ากว่าอย่างมากcount(x)คุณสามารถเพิ่มความเร็วการสืบค้นนี้ได้โดยรักษาค่า x ไว้ในตารางอื่นเช่นtable_name_x_counts (x integer not null, x_count int not null)ใช้ทริกเกอร์ แต่ประสิทธิภาพการเขียนของคุณจะได้รับผลกระทบและหากคุณอัปเดตหลาย ๆxค่าในการทำธุรกรรมเดียวคุณต้องดำเนินการนี้ในลำดับที่ชัดเจนเพื่อหลีกเลี่ยงการหยุดชะงักที่อาจเกิดขึ้น


0

ฉันยังค้นหาคำตอบเดียวกันเพราะในบางช่วงเวลาฉันต้องการtotal_count ด้วยค่าที่แตกต่างพร้อมกับลิมิต / ออฟเซ็ตที่มีค่าที่แตกต่างกันพร้อมกับขีด จำกัด

เพราะมันยากที่จะทำเพื่อให้ได้จำนวนทั้งหมดด้วยค่าที่แตกต่างพร้อมกับลิมิต / ออฟเซ็ต โดยปกติจะเป็นการยากที่จะได้รับการนับทั้งหมดด้วยการ จำกัด / ชดเชย ในที่สุดฉันก็ได้วิธีทำ -

SELECT DISTINCT COUNT(*) OVER() as total_count, * FROM table_name limit 2 offset 0;

ประสิทธิภาพการค้นหายังสูง

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