श्रेणी प्रकार पर सटीक समानता के कारण खराब क्वेरी योजना को कैसे संभालना है?


28

मैं एक अद्यतन कर रहा हूँ जहाँ मुझे एक tstzrangeचर पर एक सटीक समानता की आवश्यकता होती है । ~ 1M पंक्तियों को संशोधित किया जाता है, और क्वेरी में ~ 13 मिनट लगते हैं। इसका परिणाम यहांEXPLAIN ANALYZE देखा जा सकता है , और वास्तविक परिणाम क्वेरी प्लानर द्वारा अनुमानित अनुमानों से बेहद अलग हैं। समस्या यह है कि सूचकांक स्कैन t_rangeएक पंक्ति को वापस करने की अपेक्षा करता है।

यह इस तथ्य से संबंधित प्रतीत होता है कि श्रेणी प्रकार के आंकड़े अन्य प्रकारों से भिन्न रूप से संग्रहीत किए जाते हैं। को देखते हुए pg_statsस्तंभ के लिए देखने के लिए, n_distinctहै -1 और अन्य क्षेत्रों (जैसे most_common_vals, most_common_freqs) खाली हैं।

हालांकि, t_rangeकहीं न कहीं आंकड़े जमा होने चाहिए । एक समान समान अद्यतन जहां मैं सटीक समानता के बजाय t_range पर 'भीतर' का उपयोग करता हूं, प्रदर्शन करने में लगभग 4 मिनट लगते हैं, और एक अलग तरह की क्वेरी योजना ( यहां देखें ) का उपयोग करता है । दूसरी क्वेरी योजना मुझे समझ में आती है क्योंकि अस्थायी तालिका में प्रत्येक पंक्ति और इतिहास तालिका का एक बड़ा अंश उपयोग किया जाएगा। इससे भी महत्वपूर्ण बात, क्वेरी प्लानर फिल्टर पर पंक्तियों की लगभग सही संख्या की भविष्यवाणी करता है 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 एक अच्छा विकल्प है, लेकिन अगर एक सटीक श्रेणी समानता वास्तव में आवश्यक थी, तो मैं एक स्मार्ट क्वेरी योजना का उपयोग करने के लिए पोस्टग्रैज को कैसे प्राप्त कर सकता हूं?

अनुक्रमित के साथ तालिका परिभाषा (अप्रासंगिक कॉलम छोड़ने):

       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 (यानी, अस्थायी तालिका ऐसी है कि समय सीमा की स्थिति को अद्यतन करने वाली प्रत्येक पंक्ति अद्यतन की जाती है)।

मैंने @ 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 EDIT

1.5 साल बाद डेटा तक पहुंच नहीं होने पर, मैंने एक ही संरचना (कोई अनुक्रमित के साथ) और समान कार्डिनैलिटी के साथ एक परीक्षण तालिका बनाई। jjanes के उत्तर ने प्रस्तावित किया कि इसका कारण अद्यतन के लिए उपयोग की गई अस्थायी तालिका का आदेश हो सकता है। मैं सीधे परिकल्पना का परीक्षण करने में असमर्थ था क्योंकि मेरे पास track_io_timing(अमेज़ॅन आरडीएस का उपयोग) तक पहुंच नहीं है ।

  1. समग्र परिणाम बहुत तेजी से (कई के एक कारक द्वारा) थे। मैं अनुमान लगा रहा हूं कि यह इरविन के जवाब के अनुरूप, सूचकांकों को हटाने के कारण है ।

  2. इस परीक्षण के मामले में, क्वेरी 1 और 2 ने मूल रूप से समान समय लिया, क्योंकि वे दोनों मर्ज में शामिल हुए थे। यही है, हैश ज्वाइन को चुनने के लिए पोस्टग्रेज के कारण जो कुछ भी था, मैं उसे ट्रिगर करने में असमर्थ था, इसलिए मुझे इस पर कोई स्पष्टता नहीं है कि पोस्टग्रेज खराब प्रदर्शन करने वाले हैश को पहले स्थान पर क्यों चुन रहा है।


1
क्या होगा यदि आपने समानता की स्थिति (a = b)को दो "शर्तों" में बदल दिया है (a @> b AND b @> a):? क्या योजना बदलती है?
ypercube y

@ypercube: योजना काफी हद तक बदल जाती है, हालांकि यह अभी भी काफी इष्टतम नहीं है - मेरा संपादन # 2 देखें।
अबेबोपारेबॉप

1
एक अन्य विचार यह होगा कि आप नियमित बाइट्री इंडेक्स को जोड़ते हैं (lower(t_range),upper(t_range))क्योंकि आप समानता की जांच करते हैं।
ypercube y

जवाबों:


9

आपकी निष्पादन योजनाओं में समय का सबसे बड़ा अंतर शीर्ष नोड पर है, स्वयं अद्यतन करें। यह बताता है कि आपका अधिकांश समय अपडेट के दौरान IO में जा रहा है। आप track_io_timingप्रश्नों को चालू करके और सत्यापित करके इसे सत्यापित कर सकते हैंEXPLAIN (ANALYZE, BUFFERS)

विभिन्न योजनाएँ अलग-अलग क्रमों में अद्यतन की जाने वाली पंक्तियाँ प्रस्तुत कर रही हैं। एक trip_idक्रम में है, और दूसरा जो भी क्रम में है वे शारीरिक रूप से टेबुल टेबल में मौजूद हैं।

अद्यतन की जा रही तालिका को ट्रिप_id कॉलम के साथ भौतिक क्रम से संबंधित लगता है, और इस क्रम में पंक्तियों को अपडेट करने से रीड-फॉरवर्ड / अनुक्रमिक रीड के साथ कुशल IO पैटर्न होता है। जबकि अस्थायी सारणी के भौतिक क्रम से बहुत से यादृच्छिक पढ़े हुए प्रतीत होते हैं।

यदि आप एक order by trip_idबयान जोड़ सकते हैं जिसने अस्थायी तालिका बनाई है, तो यह आपके लिए समस्या का समाधान कर सकता है।

UPDATE ऑपरेशन की योजना बनाते समय PostgreSQL IO ऑर्डर के प्रभावों को ध्यान में नहीं रखता है। (सेलेक्ट ऑपरेशन्स के विपरीत, जहाँ यह उन्हें ध्यान में रखता है)। यदि PostgreSQL चतुर था, तो या तो यह महसूस करेगा कि एक योजना एक अधिक कुशल आदेश का उत्पादन करती है, या यह अपडेट और उसके बच्चे नोड के बीच एक स्पष्ट प्रकार के नोड को रोक देगा ताकि अपडेट को ctid क्रम में तंग पंक्तियाँ मिलें।

आप सही हैं कि PostgreSQL एक खराब काम करता है जो सीमाओं पर समानता की चयनात्मकता का अनुमान लगाता है। हालाँकि, यह केवल आपकी मूलभूत समस्या से संबंधित है। आपके अपडेट के चुनिंदा हिस्से पर एक अधिक कुशल क्वेरी गलती से अपडेट को उचित क्रम में पंक्तियों को फीड करने के लिए हो सकती है, लेकिन यदि ऐसा है तो ज्यादातर भाग्य के लिए नीचे है।


दुर्भाग्य से मैं संशोधित करने में असमर्थ हूं track_io_timing, और (क्योंकि यह डेढ़ साल हो गया है!) अब मेरे पास मूल डेटा तक पहुंच नहीं है। हालाँकि, मैंने एक ही स्कीमा और समान आकार (लाखों पंक्तियों) के साथ तालिकाओं का निर्माण करके और दो अलग-अलग अपडेट चलाकर आपके सिद्धांत का परीक्षण किया - एक जिसमें अस्थायी अद्यतन तालिका को मूल तालिका की तरह सॉर्ट किया गया था, और दूसरा जिसमें इसे सॉर्ट किया गया था अर्ध बेतरतीब ढंग से। दुर्भाग्य से, दो अपडेट में लगभग समान समय लगता है, जिसका अर्थ है कि अपडेट टेबल का ऑर्डर इस क्वेरी को प्रभावित नहीं करता है।
abeboparebop

7

मुझे बिल्कुल यकीन नहीं है कि एक समानता विधेय की चयनात्मकता tstzrangeकॉलम पर GiST सूचकांक द्वारा इतनी अधिक मौलिक क्यों है । जबकि यह प्रति सेकेण्ड दिलचस्प है, यह आपके विशेष मामले के लिए अप्रासंगिक लगता है।

चूंकि आपकी UPDATEसभी मौजूदा 3M पंक्तियों में से एक तिहाई (!) को संशोधित करता है, इसलिए एक इंडेक्स मदद करने वाला नहीं है । इसके विपरीत, तालिका के अतिरिक्त अनुक्रमणिका को क्रमिक रूप से अपडेट करने से आपकी लागत में पर्याप्त वृद्धि होने वाली है UPDATE

बस अपनी सरल क्वेरी 1 रखेंसूचकांक से पहले सूचकांक को गिराना सरल, मौलिक समाधान है । यदि आपको अन्य उद्देश्यों के लिए इसकी आवश्यकता है, तो इसे फिर से बनाएं । यह अभी भी बड़े के दौरान सूचकांक को बनाए रखने की तुलना में तेज होगा ।UPDATEUPDATEUPDATE

एक के लिए UPDATEसभी पंक्तियों का एक तिहाई पर, यह शायद ही साथ अन्य सभी अनुक्रमित ड्रॉप करने का भुगतान करेगा - और उन्हें बाद फिर से बनाते हैं UPDATE। केवल नकारात्मक पक्ष: आपको अतिरिक्त विशेषाधिकारों और मेज पर एक विशेष लॉक की आवश्यकता है (केवल एक संक्षिप्त क्षण के लिए यदि आप उपयोग करते हैं CREATE INDEX CONCURRENTLY)।

@ ytcube का GiST इंडेक्स के बजाय btree का उपयोग करने का विचार प्रिंसिपल में अच्छा लगता है। लेकिन सभी पंक्तियों में से एक तिहाई के लिए नहीं (जहां कोई सूचकांक शुरू करने के लिए कोई भी अच्छा नहीं है ), और कि सिर्फ इसलिए (lower(t_range),upper(t_range)), क्योंकि tstzrangeअसतत प्रकार नहीं है।

अधिकांश असतत श्रेणी प्रकारों में एक विहित रूप होता है, जो "समानता" की अवधारणा को सरल बनाता है: विहित रूप में मूल्य के निचले और ऊपरी हिस्से इसे परिभाषित करते हैं। दस्तावेज़ीकरण:

एक असतत रेंज प्रकार में एक विहितिकरण फ़ंक्शन होना चाहिए जो तत्व प्रकार के लिए वांछित चरण आकार से अवगत है। कैनोनिज़लाइज़ेशन फ़ंक्शन को समान प्रतिनिधित्व वाले विशेष प्रकार के समावेशी या अनन्य सीमा में होने के लिए रेंज प्रकार के बराबर मूल्यों को परिवर्तित करने के साथ चार्ज किया जाता है। यदि एक विहितकरण फ़ंक्शन निर्दिष्ट नहीं किया गया है, तो विभिन्न स्वरूपण के साथ पर्वतमाला को हमेशा असमान माना जाएगा, भले ही वे वास्तविकता में समान सेट का प्रतिनिधित्व करते हों।

बिल्ट-इन रेंज प्रकार 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रैपर फ़ंक्शन में अतिरिक्त कदम रखने की आवश्यकता होगी जो एक विहित रूप का उत्पादन करता है, और उस पर एक कार्यात्मक सूचकांक बनाता है ...

अतिरिक्त उपाय / वैकल्पिक विचार

यदि आपकी कुछ अद्यतन पंक्तियों से अधिक के लिए shape_dist_traveledपहले से ही मान tt.shape_dist_traveledहो सकता है (और आप अपने UPDATEजैसे ट्रिगर के किसी भी दुष्प्रभाव पर भरोसा नहीं करते हैं ...), तो आप खाली अपडेट को छोड़कर अपनी क्वेरी को तेज कर सकते हैं:

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

बेशक, प्रदर्शन अनुकूलन के लिए सभी सामान्य सलाह लागू होती है। The Postgres Wiki एक अच्छा प्रारंभिक बिंदु है।

VACUUM FULLआपके लिए जहर होगा, क्योंकि कुछ मृत टुपल्स (या आरक्षित स्थान FILLFACTOR) UPDATEप्रदर्शन के लिए फायदेमंद है ।

उस कई अद्यतन पंक्तियों के साथ, और यदि आप इसे (कोई समवर्ती पहुँच या अन्य निर्भरताएँ) बर्दाश्त कर सकते हैं, तो जगह में अपडेट करने के बजाय पूरी तरह से नई तालिका लिखना और भी तेज़ हो सकता है। इस संबंधित उत्तर में निर्देश:

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.