คิวรีช้าลงบนโต๊ะขนาดใหญ่ที่มี GROUP BY และ ORDER BY


14

ฉันมีตารางที่มี 7.2 ล้านสิ่งอันดับซึ่งมีลักษณะดังนี้:

                               table public.methods
 column |          type         |                      attributes
--------+-----------------------+----------------------------------------------------
 id     | integer               | not null DEFAULT nextval('methodkey'::regclass)
 hash   | character varying(32) | not null
 string | character varying     | not null
 method | character varying     | not null
 file   | character varying     | not null
 type   | character varying     | not null
Indexes:
    "methods_pkey" PRIMARY KEY, btree (id)
    "methodhash" btree (hash)

ตอนนี้ฉันต้องการเลือกค่าบางอย่าง แต่แบบสอบถามช้าอย่างไม่น่าเชื่อ:

db=# explain 
    select hash, string, count(method) 
    from methods 
    where hash not in 
          (select hash from nostring) 
    group by hash, string 
    order by count(method) desc;
                                            QUERY PLAN
----------------------------------------------------------------------------------------
 Sort  (cost=160245190041.10..160245190962.07 rows=368391 width=182)
   Sort Key: (count(methods.method))
   ->  GroupAggregate  (cost=160245017241.77..160245057764.73 rows=368391 width=182)
       ->  Sort  (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
             Sort Key: methods.hash, methods.string
             ->  Seq Scan on methods  (cost=0.00..160243305942.27 rows=3683905 width=182)
                   Filter: (NOT (SubPlan 1))
                   SubPlan 1
                   ->  Materialize  (cost=0.00..41071.54 rows=970636 width=33)
                     ->  Seq Scan on nostring  (cost=0.00..28634.36 rows=970636 width=33)

hashคอลัมน์กัญชา md5 ของstringและมีดัชนี ดังนั้นฉันคิดว่าปัญหาของฉันคือตารางทั้งหมดเรียงลำดับตาม id ไม่ใช่แฮชดังนั้นจึงใช้เวลาสักครู่ในการจัดเรียงก่อนแล้วจึงจัดกลุ่ม

ตารางnostringนี้มีรายการแฮชที่ฉันไม่ต้องการเท่านั้น แต่ฉันต้องการทั้งสองตารางเพื่อให้มีค่าทั้งหมด ดังนั้นจึงไม่มีตัวเลือกให้ลบสิ่งเหล่านี้

ข้อมูลเพิ่มเติม: ไม่มีคอลัมน์ใดที่สามารถเป็นโมฆะ (แก้ไขได้ในคำจำกัดความของตาราง) และฉันใช้ postgresql 9.2


1
ระบุรุ่นของ PostgreSQL ที่คุณใช้เสมอ เปอร์เซ็นต์ของNULLค่าในคอลัมน์methodคืออะไร? มีการทำซ้ำstringหรือไม่
Erwin Brandstetter

คำตอบ:


18

LEFT JOINใน@ คำตอบของ Dezsoควรจะดี อย่างไรก็ตามดัชนีแทบจะไม่เป็นประโยชน์ (ต่อ se) เนื่องจากแบบสอบถามต้องอ่านทั้งตารางอย่างไรก็ตามข้อยกเว้นการสแกนแบบดัชนีเท่านั้นใน Postgres 9.2+ และเงื่อนไขที่ดีดูด้านล่าง

SELECT m.hash, m.string, count(m.method) AS method_ct
FROM   methods m
LEFT   JOIN nostring n USING (hash)
WHERE  n.hash IS NULL
GROUP  BY m.hash, m.string 
ORDER  BY count(m.method) DESC;

เรียกใช้EXPLAIN ANALYZEแบบสอบถาม หลายครั้งที่จะไม่รวมเอฟเฟ็กต์และเสียงรบกวน เปรียบเทียบผลลัพธ์ที่ดีที่สุด

สร้างดัชนีหลายคอลัมน์ที่ตรงกับคำค้นหาของคุณ:

CREATE INDEX methods_cluster_idx ON methods (hash, string, method);

รอ? หลังจากที่ฉันบอกว่าดัชนีจะไม่ช่วยเหรอ เราต้องการมันไปCLUSTERที่โต๊ะ:

CLUSTER methods USING methods_cluster_idx;
ANALYZE methods;

EXPLAIN ANALYZEวิ่งใหม่ เร็วขึ้นไหม มันควรจะเป็น.

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

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

CLUSTERเป็นเครื่องมือที่ค่อนข้างหยาบต้องการล็อคพิเศษบนโต๊ะ หากคุณไม่สามารถจ่ายได้ให้พิจารณาว่าpg_repackสามารถทำสิ่งใดได้บ้างโดยไม่ต้องล็อคแบบเอกสิทธิ์ คำตอบเพิ่มเติมในภายหลังนี้:


หากเปอร์เซ็นต์ของNULLค่าในคอลัมน์methodสูง (มากกว่า ~ 20 เปอร์เซ็นต์ขึ้นอยู่กับขนาดแถวจริง) ดัชนีบางส่วนควรช่วย:

CREATE INDEX methods_foo_idx ON methods (hash, string)
WHERE method IS NOT NULL;

(การอัปเดตในภายหลังของคุณแสดงให้เห็นว่าคอลัมน์ของคุณเป็นNOT NULLดังนั้นจึงไม่เกี่ยวข้อง)

ถ้าคุณกำลังเรียกใช้ PostgreSQL 9.2หรือสูงกว่า (เป็น@deszo แสดงความคิดเห็น ) ดัชนีที่นำเสนออาจจะเป็นประโยชน์โดยไม่ต้องCLUSTERถ้าวางแผนสามารถใช้สแกนดัชนีเท่านั้น ใช้งานได้ภายใต้เงื่อนไขที่เอื้ออำนวยเท่านั้น: ไม่มีการดำเนินการเขียนที่จะส่งผลต่อการมองเห็นแผนที่ตั้งแต่VACUUMคอลัมน์สุดท้ายและคอลัมน์ทั้งหมดในแบบสอบถามจะต้องครอบคลุมโดยดัชนี โดยทั่วไปตารางแบบอ่านอย่างเดียวสามารถใช้งานนี้ได้ตลอดเวลาในขณะที่ตารางที่เขียนเป็นจำนวนมากจะถูก จำกัด รายละเอียดเพิ่มเติมใน Postgres Wiki

ดัชนีบางส่วนที่กล่าวมาอาจมีประโยชน์มากกว่าในกรณีดังกล่าว

หากในอีกทางหนึ่งไม่มี NULLค่าในคอลัมน์methodคุณควร
1) กำหนดNOT NULLและ
2) ใช้count(*)แทนcount(method)นั่นคือเร็วขึ้นเล็กน้อยและทำเช่นเดียวกันในกรณีที่ไม่มีNULLค่า

หากMATERIALIZED VIEWคุณมีการโทรสอบถามนี้และมักจะตารางเป็นแบบอ่านอย่างเดียวสร้าง


จุดที่แปลกใหม่: ตารางของคุณชื่อnostringแต่ดูเหมือนว่าจะมีแฮช การยกเว้นแฮชแทนสตริงมีโอกาสที่คุณจะแยกสตริงได้มากกว่าที่ตั้งใจ ไม่น่าเป็นไปได้อย่างยิ่งแต่เป็นไปได้


กับคลัสเตอร์มันเร็วกว่ามาก ยังคงต้องการ arround 5 นาทีสำหรับการสืบค้น แต่วิธีนั้นดีกว่าการใช้มันตลอดทั้งคืน: D
reox

@reox: เนื่องจากคุณเรียกใช้ v9.2: คุณทดสอบกับดัชนีเท่านั้นก่อนทำการรวมกลุ่มหรือไม่ จะน่าสนใจถ้าคุณเห็นความแตกต่าง (คุณไม่สามารถสร้างความแตกต่างได้หลังจากทำคลัสเตอร์) นอกจากนี้ (และสิ่งนี้จะมีราคาถูก) อธิบายว่าจะแสดงการสแกนดัชนีหรือการสแกนเต็มตารางทันทีหรือไม่
Erwin Brandstetter

5

ยินดีต้อนรับสู่ DBA.SE!

คุณสามารถลองใช้ข้อความค้นหาของคุณใหม่เช่นนี้:

SELECT m.hash, string, count(method) 
FROM 
    methods m
    LEFT JOIN nostring n ON m.hash = n.hash
WHERE n.hash IS NULL
GROUP BY hash, string 
ORDER BY count(method) DESC;

หรือความเป็นไปได้อื่น:

SELECT m.hash, string, count(method) 
FROM 
    methods m
WHERE NOT EXISTS (SELECT hash FROM nostring WHERE hash = m.hash)
GROUP BY hash, string 
ORDER BY count(method) DESC;

NOT IN เป็น sink ทั่วไปสำหรับประสิทธิภาพเนื่องจากยากต่อการใช้ดัชนีกับมัน

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


ดัชนีถูกสร้างขึ้นใน nostring.hash allready แต่ฉันคิดว่า postgres อย่าใช้เพราะมีสิ่งอันดับมากเกินไป ... เมื่อฉัน explcit ปิดการใช้งานการสแกนตามลำดับจะใช้ดัชนี ถ้าฉันใช้ซ้ายเข้าร่วมฉันจะได้รับค่าใช้จ่าย 32 ล้านดังนั้นมันจะดีกว่า ... แต่ฉันพยายามที่จะเพิ่มประสิทธิภาพมากขึ้น ...
reox

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

1

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

บุคคลอื่นสร้างฟังก์ชัน pl / pgsql ที่แปลงค่า (ส่วนหนึ่ง) เป็นค่า md5 จากข้อความเป็นสตริง ดู/programming/9809381/hashing-a-string-to-a-numeric-value-in-postgressqlสำหรับตัวอย่าง

ฉันเชื่อว่าคุณใช้เวลามากในการเปรียบเทียบสตริงในขณะที่สแกนดัชนี หากคุณจัดการเก็บค่านั้นเป็นตัวเลขก็ควรจะเร็วขึ้นจริงๆ


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

2
ฉันคิดว่าฉันจะเก็บ md5 เป็น bytea แทนที่จะเป็นจำนวนเพื่อประสิทธิภาพของพื้นที่: sqlfiddle.com/#!12/d41d8/252
แจ็คบอกว่าลอง topanswers.xyz

ยังยินดีต้อนรับสู่ dba.se!
แจ็คบอกว่าลอง topanswers.xyz

@JackDouglas: ความคิดเห็นที่น่าสนใจ! 16 ไบต์ต่อ md5 แทนที่จะเป็น 32 เป็นบิตสำหรับตารางขนาดใหญ่
Erwin Brandstetter

0

ฉันพบปัญหานี้มากและค้นพบเคล็ดลับง่ายๆ 2 ส่วน

  1. สร้างดัชนีซับสตริงในค่าแฮช: (7 มักจะเป็นความยาวที่ดี)

    create index methods_idx_hash_substring ON methods(substring(hash,1,7))

  2. ให้การค้นหา / การรวมของคุณมีการจับคู่สตริงย่อยดังนั้นตัววางแผนคิวรีจะบอกเป็นนัยให้ใช้ดัชนี:

    อายุ: WHERE hash = :kwarg

    ใหม่: WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))

คุณควรมีดัชนีในวัตถุดิบhashด้วยเช่นกัน

ผลลัพธ์ (โดยปกติ) คือผู้วางแผนจะศึกษาดัชนีย่อยก่อนและแยกแถวส่วนใหญ่ออก จากนั้นจะจับคู่แฮชแบบเต็ม 32 อักขระเข้ากับดัชนีที่เกี่ยวข้อง (หรือตาราง) วิธีนี้ลดการสืบค้น 800ms ลงเหลือ 4 ข้อสำหรับฉัน

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