วิธีจัดการกับแผนแบบสอบถามที่ไม่ดีที่เกิดจากความเท่าเทียมกันที่แน่นอนในประเภทช่วง?


28

ฉันกำลังอัปเดตโดยที่ฉันต้องการความเท่าเทียมกันแน่นอนในtstzrangeตัวแปร แถว ~ 1M มีการแก้ไขและแบบสอบถามใช้เวลาประมาณ 13 นาที ผลลัพธ์ของEXPLAIN ANALYZEสามารถเห็นได้ที่นี่และผลลัพธ์ที่แท้จริงแตกต่างอย่างมากจากที่ประเมินโดยผู้วางแผนแบบสอบถาม ปัญหาคือการสแกนดัชนีt_rangeคาดว่าจะส่งคืนแถวเดียว

สิ่งนี้น่าจะเกี่ยวข้องกับความจริงที่ว่าสถิติของประเภทช่วงนั้นถูกจัดเก็บแตกต่างจากประเภทอื่น ๆ มองไปที่pg_statsมุมมองสำหรับคอลัมน์ที่n_distinctเป็น -1 และสาขาอื่น ๆ (เช่นmost_common_vals, most_common_freqs) เป็นที่ว่างเปล่า

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

การกระจายตัวของt_rangeมันค่อนข้างผิดปกติ ฉันใช้ตารางนี้เพื่อเก็บสถานะทางประวัติศาสตร์ของตารางอื่นและการเปลี่ยนแปลงของตารางอื่น ๆ เกิดขึ้นพร้อมกันในการทิ้งขนาดใหญ่ดังนั้นจึงมีค่าที่แตกต่างกันไม่มากt_rangeนัก นี่คือการนับที่สอดคล้องกับค่าที่ไม่ซ้ำกันของt_range:

                              t_range                              |  count  
-------------------------------------------------------------------+---------
 ["2014-06-12 20:58:21.447478+00","2014-06-27 07:00:00+00")        |  994676
 ["2014-06-12 20:58:21.447478+00","2014-08-01 01:22:14.621887+00") |   36791
 ["2014-06-27 07:00:00+00","2014-08-01 07:00:01+00")               | 1000403
 ["2014-06-27 07:00:00+00",infinity)                               |   36791
 ["2014-08-01 07:00:01+00",infinity)                               |  999753

การนับสำหรับความแตกต่างt_rangeด้านบนเสร็จสมบูรณ์ดังนั้นความสำคัญของหัวใจคือ ~ 3M (ซึ่ง ~ 1M จะได้รับผลกระทบจากข้อความค้นหาอัปเดตใด ๆ )

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

คำจำกัดความของตารางที่มีดัชนี (ดร็อปคอลัมน์ที่ไม่เกี่ยวข้อง):

       Column        |   Type    |                                  Modifiers                                   
---------------------+-----------+------------------------------------------------------------------------------
 history_id          | integer   | not null default nextval('gtfs_stop_times_history_history_id_seq'::regclass)
 t_range             | tstzrange | not null
 trip_id             | text      | not null
 stop_sequence       | integer   | not null
 shape_dist_traveled | real      | 
Indexes:
    "gtfs_stop_times_history_pkey" PRIMARY KEY, btree (history_id)
    "gtfs_stop_times_history_t_range" gist (t_range)
    "gtfs_stop_times_history_trip_id" btree (trip_id)

แบบสอบถาม 1:

UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND sth.t_range = '["2014-08-01 07:00:01+00",infinity)'::tstzrange;

แบบสอบถาม 2:

UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND '2014-08-01 07:00:01+00'::timestamptz <@ sth.t_range;

Q1 ปรับปรุง 999753 แถวและปรับปรุง Q2 999753 + 36791 = 1036544 (เช่นตาราง temp เป็นเช่นนั้นทุกแถวที่ตรงกับเงื่อนไขช่วงเวลาจะถูกปรับปรุง)

ฉันลองใช้แบบสอบถามนี้เพื่อตอบสนองต่อความคิดเห็นของ @ ypercube :

แบบสอบถาม 3:

UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND sth.t_range <@ '["2014-08-01 07:00:01+00",infinity)'::tstzrange
AND '["2014-08-01 07:00:01+00",infinity)'::tstzrange <@ sth.t_range;

แผนแบบสอบถามและผลลัพธ์ (ดูที่นี่ ) อยู่ตรงกลางระหว่างสองกรณีก่อนหน้านี้ (~ 6 นาที)

2016/02/05 แก้ไข

ไม่สามารถเข้าถึงข้อมูลได้อีกต่อไปหลังจาก 1.5 ปีฉันสร้างตารางทดสอบที่มีโครงสร้างเดียวกัน (โดยไม่มีดัชนี) และ cardinality ที่คล้ายกัน คำตอบของ jjanesเสนอว่าสาเหตุอาจเป็นลำดับของตารางชั่วคราวที่ใช้สำหรับการอัพเดต ฉันไม่สามารถทดสอบสมมติฐานได้โดยตรงเพราะฉันไม่สามารถเข้าถึงtrack_io_timing(ใช้ Amazon RDS)

  1. ผลลัพธ์โดยรวมนั้นเร็วขึ้นมาก (จากหลายปัจจัย) ฉันคาดเดาว่าเป็นเพราะการกำจัดของดัชนีที่สอดคล้องกับคำตอบของเออร์วิน

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


1
เกิดอะไรขึ้นถ้าคุณแปลงสภาพความเสมอภาค(a = b)สอง "มี" เงื่อนไข(a @> b AND b @> a)? แผนเปลี่ยนแปลงหรือไม่?
ypercubeᵀᴹ

@ypercube: แผนการเปลี่ยนแปลงอย่างมีนัยสำคัญถึงแม้ว่ามันจะยังไม่ดีที่สุด - ดูการแก้ไขของฉัน # 2
abeboparebop

1
อีกแนวคิดหนึ่งคือการเพิ่มดัชนี btree เป็นประจำ(lower(t_range),upper(t_range))เนื่องจากคุณตรวจสอบความเท่าเทียมกัน
ypercubeᵀᴹ

คำตอบ:


9

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

แผนการที่แตกต่างกันจะนำเสนอแถวที่จะปรับปรุงในคำสั่งซื้อที่แตกต่างกัน หนึ่งคือในการtrip_idสั่งซื้อและอื่น ๆ ที่อยู่ในลำดับใด ๆ ที่พวกเขาจะปรากฏอยู่ในตารางอุณหภูมิ

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

หากคุณสามารถเพิ่มorder by trip_idคำสั่งที่สร้างตารางชั่วคราวนั่นอาจแก้ไขปัญหาให้คุณได้

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

คุณถูกต้องที่ PostgreSQL ทำงานได้ไม่ดีประมาณการเลือกของความเท่าเทียมกันเข้าร่วมในช่วง อย่างไรก็ตามสิ่งนี้เกี่ยวข้องกับปัญหาพื้นฐานของคุณอย่างเป็นรูปธรรมเท่านั้น แบบสอบถามที่มีประสิทธิภาพมากขึ้นในส่วนที่เลือกของการอัปเดตของคุณอาจบังเอิญเกิดขึ้นกับฟีดแถวเข้าสู่การอัปเดตที่เหมาะสมในลำดับที่ดีขึ้น แต่ถ้าเป็นเช่นนั้น


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

7

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

เนื่องจากคุณUPDATEปรับเปลี่ยนหนึ่งในสาม (!) ของแถว 3M ที่มีอยู่ทั้งหมดดัชนีจะไม่ช่วยเลย ในทางตรงกันข้ามการปรับปรุงดัชนีเพิ่มเติมนอกเหนือจากตารางจะเพิ่มต้นทุนให้กับคุณUPDATEอย่างมาก

เพียงแค่ให้คุณง่ายแบบสอบถาม 1 ง่ายที่รุนแรงแก้ปัญหาคือการลดลงของดัชนีUPDATEก่อน หากคุณจำเป็นต้องใช้มันเพื่อวัตถุประสงค์อื่น ๆ UPDATEอีกสร้างมันขึ้นมาหลังจากที่ UPDATEนี้จะยังคงมีการรักษาได้เร็วกว่าดัชนีในช่วงที่มีขนาดใหญ่

สำหรับUPDATEในสามของแถวทั้งหมดก็อาจจะจ่ายจะลดลงดัชนีอื่น ๆ ทั้งหมดเป็นอย่างดี - UPDATEและสร้างใหม่อีกครั้งหลังจากที่พวกเขา ข้อเสียเพียงอย่างเดียว: คุณต้องการสิทธิ์เพิ่มเติมและล็อคพิเศษบนโต๊ะ (สำหรับช่วงเวลาสั้น ๆ ถ้าคุณใช้CREATE INDEX CONCURRENTLY)

แนวคิดของ @ ypercube ในการใช้ btree แทนดัชนี GiST นั้นดีมาก แต่ไม่ใช่สำหรับหนึ่งในสามของแถวทั้งหมด (โดยที่ไม่มีดัชนีจะเริ่มต้นด้วยดี) และไม่ใช่เพียง(lower(t_range),upper(t_range))เพราะtstzrangeไม่ใช่ประเภทช่วงที่ไม่ต่อเนื่อง

ประเภทช่วงไม่ต่อเนื่องส่วนใหญ่มีรูปแบบบัญญัติซึ่งทำให้แนวคิดของ "ความเท่าเทียมกัน" ง่ายขึ้น: ขอบเขตล่างและบนของค่าในรูปแบบมาตรฐานกำหนดมัน เอกสารประกอบ:

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

ในตัวชนิดช่วงint4range, int8rangeและdaterangeการใช้งานทุกรูปแบบที่เป็นที่ยอมรับว่ามีการผูกพันที่ลดลงและไม่รวมผูกพันบน; นั่นคือ, [). อย่างไรก็ตามประเภทช่วงที่ผู้ใช้กำหนดสามารถใช้ข้อกำหนดอื่น ๆ ได้

นี่ไม่ใช่กรณีtstzrangeที่ต้องคำนึงถึงความเสมอภาคของขอบเขตบนและล่างเพื่อความเท่าเทียมกัน ดัชนี btree ที่เป็นไปได้ต้องอยู่ใน:

(lower(t_range), upper(t_range), lower_inc(t_range), upper_inc(t_range))

และแบบสอบถามจะต้องใช้การแสดงออกที่เหมือนกันในWHEREข้อ

อาจมีการล่อลวงให้ทำดัชนีเพียงค่าทั้งหมดที่ส่งไปยังtext: (cast(t_range AS text))- แต่นิพจน์นี้ไม่ได้IMMUTABLEเนื่องจากการแสดงข้อความของtimestamptzค่าขึ้นอยู่กับการtimezoneตั้งค่าปัจจุบัน คุณจะต้องใส่ขั้นตอนเพิ่มเติมลงในIMMUTABLEฟังก์ชั่น wrapper ที่สร้างแบบฟอร์มมาตรฐานและสร้างดัชนีการทำงานบน ...

มาตรการเพิ่มเติม / แนวคิดทางเลือก

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

WHERE ...
AND   shape_dist_traveled IS DISTINCT FROM tt.shape_dist_traveled;

แน่นอนว่าคำแนะนำทั่วไปทั้งหมดสำหรับการเพิ่มประสิทธิภาพมีผลบังคับใช้ Postgres Wiki เป็นจุดเริ่มต้นที่ดี

VACUUM FULLจะเป็นพิษสำหรับคุณเนื่องจากบาง tuples ที่ตายแล้ว (หรือพื้นที่ที่สงวนไว้FILLFACTOR) เป็นประโยชน์ต่อการUPDATEแสดง

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

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