แบบสอบถาม JOIN ที่เรียบง่ายช้ามาก


12

โครงสร้าง DB แบบง่าย (สำหรับฟอรัมออนไลน์):

CREATE TABLE users (
    id integer NOT NULL PRIMARY KEY,
    username text
);
CREATE INDEX ON users (username);

CREATE TABLE posts (
    id integer NOT NULL PRIMARY KEY,
    thread_id integer NOT NULL REFERENCES threads (id),
    user_id integer NOT NULL REFERENCES users (id),
    date timestamp without time zone NOT NULL,
    content text
);
CREATE INDEX ON posts (thread_id);
CREATE INDEX ON posts (user_id);

ประมาณ 80k รายการในusersและ 2,6 ล้านรายการในpostsตาราง แบบสอบถามง่ายๆนี้เพื่อให้ผู้ใช้ 100 คนแรกโดยโพสต์ของพวกเขาใช้เวลา2,4 วินาที :

EXPLAIN ANALYZE SELECT u.id, u.username, COUNT(p.id) AS PostCount FROM users u
                    INNER JOIN posts p on p.user_id = u.id
                    WHERE u.username IS NOT NULL
                    GROUP BY u.id
ORDER BY PostCount DESC LIMIT 100;
Limit  (cost=316926.14..316926.39 rows=100 width=20) (actual time=2326.812..2326.830 rows=100 loops=1)
  ->  Sort  (cost=316926.14..317014.83 rows=35476 width=20) (actual time=2326.809..2326.820 rows=100 loops=1)
        Sort Key: (count(p.id)) DESC
        Sort Method: top-N heapsort  Memory: 32kB
        ->  HashAggregate  (cost=315215.51..315570.27 rows=35476 width=20) (actual time=2311.296..2321.739 rows=34608 loops=1)
              Group Key: u.id
              ->  Hash Join  (cost=1176.89..308201.88 rows=1402727 width=16) (actual time=16.538..1784.546 rows=1910831 loops=1)
                    Hash Cond: (p.user_id = u.id)
                    ->  Seq Scan on posts p  (cost=0.00..286185.34 rows=1816634 width=8) (actual time=0.103..1144.681 rows=2173916 loops=1)
                    ->  Hash  (cost=733.44..733.44 rows=35476 width=12) (actual time=15.763..15.763 rows=34609 loops=1)
                          Buckets: 65536  Batches: 1  Memory Usage: 2021kB
                          ->  Seq Scan on users u  (cost=0.00..733.44 rows=35476 width=12) (actual time=0.033..6.521 rows=34609 loops=1)
                                Filter: (username IS NOT NULL)
                                Rows Removed by Filter: 11335

Execution time: 2301.357 ms

ด้วยset enable_seqscan = falseยิ่งแย่กว่านั้น:

Limit  (cost=1160881.74..1160881.99 rows=100 width=20) (actual time=2758.086..2758.107 rows=100 loops=1)
  ->  Sort  (cost=1160881.74..1160970.43 rows=35476 width=20) (actual time=2758.084..2758.098 rows=100 loops=1)
        Sort Key: (count(p.id)) DESC
        Sort Method: top-N heapsort  Memory: 32kB
        ->  GroupAggregate  (cost=0.79..1159525.87 rows=35476 width=20) (actual time=0.095..2749.859 rows=34608 loops=1)
              Group Key: u.id
              ->  Merge Join  (cost=0.79..1152157.48 rows=1402727 width=16) (actual time=0.036..2537.064 rows=1910831 loops=1)
                    Merge Cond: (u.id = p.user_id)
                    ->  Index Scan using users_pkey on users u  (cost=0.29..2404.83 rows=35476 width=12) (actual time=0.016..41.163 rows=34609 loops=1)
                          Filter: (username IS NOT NULL)
                          Rows Removed by Filter: 11335
                    ->  Index Scan using posts_user_id_index on posts p  (cost=0.43..1131472.19 rows=1816634 width=8) (actual time=0.012..2191.856 rows=2173916 loops=1)
Planning time: 1.281 ms
Execution time: 2758.187 ms

จัดกลุ่มตามusernameหายไปใน Postgres เพราะไม่จำเป็น (SQL Server บอกว่าฉันต้องจัดกลุ่มตามusernameหากฉันต้องการเลือกชื่อผู้ใช้) การจัดกลุ่มกับusernameเพิ่ม ms เล็กน้อยให้กับเวลาดำเนินการบน Postgres หรือไม่ทำอะไรเลย

สำหรับวิทยาศาสตร์ผมได้ติดตั้ง Microsoft SQL Server ไปยังเซิร์ฟเวอร์เดียวกัน (ซึ่งวิ่ง ArchLinux, Xeon 8 หลัก 24 GB RAM, SSD) และอพยพข้อมูลทั้งหมดจาก Postgres - เดียวกันโครงสร้างตารางเดียวกันดัชนีเดียวกันข้อมูล ข้อความค้นหาเดียวกันเพื่อให้ผู้โพสต์ 100 อันดับแรกทำงานใน0.3 วินาที :

SELECT TOP 100 u.id, u.username, COUNT(p.id) AS PostCount FROM dbo.users u
                    INNER JOIN dbo.posts p on p.user_id = u.id
                    WHERE u.username IS NOT NULL
                    GROUP BY u.id, u.username
ORDER BY PostCount DESC

ให้ผลลัพธ์ที่เหมือนกันจากข้อมูลเดียวกัน แต่ทำได้เร็วขึ้น 8 เท่า และเป็นรุ่นเบต้าของ MS SQL บน Linux ฉันเดาว่ามันจะทำงานบน "บ้าน" OS - Windows Server - มันอาจเร็วกว่านี้

แบบสอบถาม PostgreSQL ของฉันผิดทั้งหมดหรือ PostgreSQL นั้นช้าหรือไม่

ข้อมูลเพิ่มเติม

เวอร์ชันเกือบเป็นรุ่นใหม่ล่าสุด (9.6.1 ปัจจุบันเป็นรุ่นล่าสุด 9.6.2, ArchLinux เพิ่งมีแพ็คเกจล้าสมัยและอัปเดตช้ามาก) Config:

max_connections = 75
shared_buffers = 3584MB       
effective_cache_size = 10752MB
work_mem = 24466kB         
maintenance_work_mem = 896MB   
dynamic_shared_memory_type = posix  
min_wal_size = 1GB
max_wal_size = 2GB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100

EXPLAIN ANALYZEผลลัพธ์: https://pastebin.com/HxucRgnk

พยายามทำดัชนีทั้งหมดที่ใช้แม้แต่ GIN และ GIST วิธีที่เร็วที่สุดสำหรับ PostgreSQL (และ Googling ยืนยันด้วยหลายแถว) คือการใช้การสแกนตามลำดับ

MS SQL Server 14.0.405.200-1, conf เริ่มต้น

ฉันใช้สิ่งนี้ใน API (ด้วยการเลือกแบบธรรมดาโดยไม่ต้องวิเคราะห์) และเรียกจุดปลายทาง API นี้ด้วยโครเมี่ยมว่าใช้เวลา 2500 มิลลิวินาที + - เพิ่ม 50 มิลลิวินาทีของ HTTP และเว็บเซิร์ฟเวอร์ค่าใช้จ่ายเหนือศีรษะ (API และ SQL ทำงานบนเซิร์ฟเวอร์เดียวกัน) - มันเหมือนกัน. ฉันไม่สนใจประมาณ 100 ms ที่นี่หรือที่นั่นสิ่งที่ฉันสนใจคือสองวินาที

explain analyze SELECT user_id, count(9) FROM posts group by user_id;ใช้เวลา 700 ms ขนาดของpostsตารางคือ 2154 MB


2
เสียงคุณมีโพสต์อ้วนดีจากผู้ใช้ของคุณ (~ 1kB โดยเฉลี่ย) อาจเหมาะสมที่จะแยกออกจากส่วนที่เหลือของpostsตารางโดยใช้ตารางแบบCREATE TABLE post_content (post_id PRIMARY KEY REFERENCES posts (id), content text); นั้นส่วนใหญ่ของ I / O ที่ 'เสีย' ในการค้นหาประเภทนี้อาจไม่ได้รับการยกเว้น หากโพสต์เล็กกว่านี้การVACUUM FULLเปิดpostsจะช่วยได้
dezso

ใช่โพสต์มีคอลัมน์เนื้อหาที่มี html ทั้งหมดของโพสต์ ขอบคุณสำหรับคำแนะนำของคุณจะลองในวันพรุ่งนี้ คำถามคือ - ตารางการโพสต์ MSSQL มีน้ำหนักมากกว่า 1.5 GB และมีรายการเนื้อหาเดียวกัน แต่จัดการได้ค่อนข้างเร็ว - ทำไม
ลาร์ส

2
คุณสามารถโพสต์แผนการดำเนินการจริงจาก SQL Server ได้เช่นกัน อาจจะน่าสนใจจริงๆถึงกับ Postgres คนอย่างฉัน
dezso

อืมคาดเดาอย่างรวดเร็วคุณสามารถเปลี่ยนสิ่งนี้GROUP BY u.idเป็นสิ่งนี้GROUP BY p.user_idและลองสิ่งนั้นได้ไหม? ฉันเดาว่า Postgres จะเข้าร่วมกลุ่มแรกและกลุ่มที่สองเพราะคุณจัดกลุ่มตามตัวระบุตารางผู้ใช้แม้ว่าคุณจะต้องการเพียงโพสต์ user_id เพื่อให้ได้แถว N - แถวบนสุด
UldisK

คำตอบ:


1

อีกหนึ่งตัวแปรคิวรี่ที่ดีคือ:

SELECT p.user_id, p.cnt AS PostCount
FROM users u
INNER JOIN (
    select user_id, count(id) as cnt from posts group by user_id
) as p on p.user_id = u.id
WHERE u.username IS NOT NULL          
ORDER BY PostCount DESC LIMIT 100;

มันไม่ใช้ประโยชน์จาก CTE และให้คำตอบที่ถูกต้อง (และตัวอย่าง CTE อาจสร้างน้อยกว่า 100 แถวในทางทฤษฎีทำให้มันเป็นข้อ จำกัด ก่อนแล้วจึงเข้าร่วมกับผู้ใช้)

ฉันคิดว่า MSSQL สามารถดำเนินการเปลี่ยนแปลงดังกล่าวได้ในเครื่องมือเพิ่มประสิทธิภาพการสืบค้นและ PostgreSQL ไม่สามารถรวมการรวมเข้าด้วยกันได้ หรือ MSSQL เพิ่งจะมีการนำแฮชเข้าร่วมเร็วกว่ามาก


8

สิ่งนี้อาจจะใช้งานได้หรือไม่ - ฉันกำลังใช้ความรู้สึกแบบนี้ว่ามันเข้าร่วมโต๊ะของคุณต่อหน้ากลุ่มและตัวกรอง ฉันขอแนะนำให้ลองทำสิ่งต่อไปนี้: ตัวกรองและกลุ่มโดยใช้ CTE ก่อนพยายามเข้าร่วม:

with
    __posts as(
        select
            user_id,
            count(1) as num_posts
        from
            posts
        group by
            user_id
        order by
            num_posts desc
        limit 100
    )
select
    users.username,
    __posts.num_posts
from
    users
    inner join __posts on(
        __posts.user_id = users.id
    )
order by
    num_posts desc

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

คำแนะนำที่ดีที่สุดที่คุณสามารถนำไปใช้ได้จริงคือลองหลายวิธีและตรวจสอบแผนการสอบถามของคุณ


-1

คุณพยายามเพิ่ม work_mem หรือไม่ ดูเหมือนว่า 24Mb จะเล็กเกินไปและดังนั้น Hash Join จึงต้องใช้หลายแบตช์ (ซึ่งเขียนด้วยไฟล์ temp)


มันไม่เล็กเกินไป เพิ่มขึ้นถึง 240 เมกะไบต์ไม่ทำอะไรเลย สิ่งที่จะช่วยใน postgresql.conf ทำให้การสืบค้นแบบขนานโดยการเพิ่มสองบรรทัดนี้max_parallel_workers_per_gather = 4และmax_worker_processes = 16
Lars
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.