पोस्टक्वेरीक्यूक्यू क्वेरी बहुत धीमी है जब उपकुंजी गयी


10

मेरे पास 1.5M पंक्तियों वाली एक अपेक्षाकृत सरल क्वेरी है:

SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;

EXPLAIN ANALYZE उत्पादन:

Limit  (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1)
  ->  Bitmap Heap Scan on publication  (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1)
        Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321))
        ->  BitmapOr  (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1)
              ->  Bitmap Index Scan on publication_pkey  (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1)
                    Index Cond: (mtid = 9762715)
              ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1)
                    Index Cond: (last_modifier = 21321)
Total runtime: 1.027 ms

अब तक बहुत अच्छा, तेज और उपलब्ध अनुक्रमित का उपयोग करता है।
अब, यदि मैं किसी क्वेरी को थोड़ा संशोधित करता हूं, तो परिणाम होगा:

SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;

EXPLAIN ANALYZEउत्पादन होता है:

Limit  (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1)
  ->  Seq Scan on publication  (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1)
        Filter: ((hashed SubPlan 1) OR (last_modifier = 21321))
        SubPlan 1
          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
Total runtime: 2841.442 ms

इतनी जल्दी नहीं, और seq स्कैन का उपयोग कर ...

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

प्रश्न

  1. दूसरे मामले में सूचकांक का उपयोग क्यों नहीं किया जा सकता है? उनका उपयोग कैसे किया जा सकता है?
  2. क्या मैं किसी अन्य तरीके से क्वेरी के प्रदर्शन में सुधार कर सकता हूं?

अतिरिक्त विचार

ऐसा लगता है कि हम पहले मामले का चयन मैन्युअल रूप से एक SELECT करके कर सकते हैं, और फिर परिणामी सूची को क्वेरी में डाल सकते हैं। IN () सूची में 5000 नंबर के साथ भी यह दूसरे समाधान से चार गुना तेज है। हालांकि, यह सिर्फ गलत लगता है (भी, यह 100 गुना तेज हो सकता है :))। यह पूरी तरह से समझ में नहीं आता है कि क्वेरी प्लानर इन दो प्रश्नों के लिए पूरी तरह से अलग तरीके का उपयोग क्यों करता है, इसलिए मैं इस समस्या का एक अच्छा समाधान खोजना चाहता हूं।


क्या आप किसी तरह अपने कोड को फिर से लिख सकते हैं ताकि हाइबरनेट JOINइसके बजाय उत्पन्न हो IN ()? इसके अलावा, publicationहाल ही में विश्लेषण किया गया है?
dezso 12

हां, मैंने दोनों VACUUM ANALYZE और VACUUM FULL किया। प्रदर्शन में कोई बदलाव नहीं हुआ। दूसरे के रूप में, AFAIR हमने कोशिश की कि यह क्वेरी के प्रदर्शन को महत्वपूर्ण रूप से प्रभावित न करे।
पी। सेपर

1
यदि हाइबरनेट एक उचित क्वेरी उत्पन्न करने में विफल रहता है, तो आप कच्चे एसक्यूएल का उपयोग क्यों नहीं करते हैं? यह Google अनुवाद पर जोर देने जैसा है जबकि आप पहले से ही बेहतर जानते हैं कि इसे अंग्रेजी में कैसे व्यक्त किया जाए। आपके प्रश्न के अनुसार: यह वास्तव में पीछे छिपी वास्तविक क्वेरी पर निर्भर करता है (SELECT 9762715)
इरविन ब्रान्डसेट्टर

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

जवाबों:


6

समस्या का मूल यहाँ स्पष्ट हो जाता है:

प्रकाशन पर Seq स्कैन (लागत = 0.01..349652.84 पंक्तियाँ = 744661 चौड़ाई = 8) (वास्तविक समय = 2735.888..2841.393 पंक्तियाँ = 1 लूप = 1)

पोस्टग्रेज 744661 पंक्तियों को वापस करने का अनुमान लगाता है, जबकि वास्तव में, यह एक पंक्ति है। यदि Postgres बेहतर नहीं जानता कि क्वेरी से क्या उम्मीद की जाए तो वह बेहतर योजना नहीं बना सकता है। हमें पीछे छिपी वास्तविक क्वेरी को देखना होगा (SELECT 9762715)- और शायद टेबल की परिभाषा, बाधाओं, कार्डिनैलिटी और डेटा वितरण को भी जानना होगा। जाहिर है, Postgres भविष्यवाणी करने के लिए कैसे में सक्षम नहीं है कुछ पंक्तियाँ यह द्वारा लौटा दी जाएगी। क्वेरी को फिर से लिखने के तरीके हो सकते हैं, जो इस पर निर्भर करता है

यदि आप जानते हैं कि उपश्रेणी nपंक्तियों से अधिक कभी नहीं लौट सकती है, तो आप केवल पोस्टग्रेज का उपयोग करके बता सकते हैं:

SELECT mtid
FROM   publication
WHERE  mtid IN (SELECT ... LIMIT n) --  OR last_modifier=21321
LIMIT  5000;

यदि n काफी छोटा है, तो Postgres (बिटमैप) इंडेक्स स्कैन में बदल जाएगा। हालाँकि , यह केवल साधारण केस के लिए काम करता है। एक ORशर्त जोड़ते समय काम करने वाले स्टॉप्स : क्वेरी प्लानर वर्तमान में उस का सामना नहीं कर सकता है।

मैं शायद ही कभी IN (SELECT ...)शुरू करने के लिए उपयोग करता हूं । आमतौर पर इसे लागू करने का एक बेहतर तरीका है, अक्सर EXISTSअर्ध-जुड़ाव के साथ। कभी-कभी ( LEFT) JOIN( LATERAL) के साथ ...

स्पष्ट वर्कअराउंड का उपयोग करना होगा UNION, लेकिन आपने इसे खारिज कर दिया। मैं वास्तविक उपश्रेणी और अन्य प्रासंगिक विवरणों को जाने बिना अधिक नहीं कह सकता।


2
वहाँ है कोई क्वेरी के पीछे छिपा हुआ (SELECT 9762715) ! यदि मैं उस सटीक क्वेरी को चलाता हूं जो आप ऊपर देखते हैं। बेशक, मूल हाइबरनेट क्वेरी थोड़ी अधिक जटिल है, लेकिन मैं (मुझे लगता है) उस बिंदु को इंगित करने में कामयाब रहा जहां क्वेरी प्लानर भटक जाता है, इसलिए मैंने क्वेरी के उस हिस्से को प्रस्तुत किया। हालांकि, उपरोक्त व्याख्या और प्रश्न शब्दशः ctrl-cv हैं।
पी। सेपर

दूसरे भाग के रूप में, आंतरिक सीमा काम नहीं करती है: EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;एक क्रमिक स्कैन भी करती है और लगभग 3 सेकंड के लिए भी चलती है ...
P.Péter

@ P.Péter: यह मेरे स्थानीय टेस्ट में पोस्टग्रेज 9.4 पर एक वास्तविक उपश्रेणी के साथ काम करता है। यदि आप जो दिखाते हैं वह आपकी वास्तविक क्वेरी है, तो आपके पास पहले से ही अपना समाधान है: अपने प्रश्न में पहली क्वेरी का उपयोग एक सबक्वेरी के बजाय एक स्थिर के साथ करें।
इरविन ब्रान्डसेट्टर 15

खैर, मैंने भी एक नई परीक्षा की मेज पर एक उपश्रेणी की कोशिश की CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;:। और प्रभाव अभी भी उसी प्रश्नों के लिए था test: किसी भी सबक्वेरी के परिणामस्वरूप एक सीक स्कैन हुआ ... मैंने 9.1 और 9.4 दोनों की कोशिश की। प्रभाव समान है।
पी। सेपर

1
@ P.Péter: मैंने फिर से परीक्षण किया और महसूस किया कि मैंने बिना किसी ORशर्त के परीक्षण किया था । LIMITकेवल सरल मामले के लिए चाल के साथ काम करता है।
एरविन ब्रान्डसेट्टर

6

मेरे सहकर्मी ने क्वेरी को बदलने का एक तरीका ढूंढ लिया है ताकि उसे एक सरल पुनर्लेखन की आवश्यकता हो और वह करे जो उसे करने की आवश्यकता है, यानी एक चरण में सबसेक्ट करना, और फिर परिणाम पर आगे के संचालन करना:

SELECT mtid FROM publication 
WHERE 
  mtid = ANY( (SELECT ARRAY(SELECT 9762715))::bigint[] )
  OR last_modifier=21321
LIMIT 5000;

व्याख्या विश्लेषण अब है:

 Limit  (cost=92.58..9442.38 rows=2478 width=8) (actual time=0.071..0.074 rows=1 loops=1)
   InitPlan 2 (returns $1)
     ->  Result  (cost=0.01..0.02 rows=1 width=0) (actual time=0.010..0.011 rows=1 loops=1)
           InitPlan 1 (returns $0)
             ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.002 rows=1 loops=1)
   ->  Bitmap Heap Scan on publication  (cost=92.56..9442.36 rows=2478 width=8) (actual time=0.069..0.070 rows=1 loops=1)
         Recheck Cond: ((mtid = ANY (($1)::bigint[])) OR (last_modifier = 21321))
         Heap Blocks: exact=1
         ->  BitmapOr  (cost=92.56..92.56 rows=2478 width=0) (actual time=0.060..0.060 rows=0 loops=1)
               ->  Bitmap Index Scan on publication_pkey  (cost=0.00..44.38 rows=10 width=0) (actual time=0.046..0.046 rows=1 loops=1)
                     Index Cond: (mtid = ANY (($1)::bigint[]))
               ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..46.94 rows=2468 width=0) (actual time=0.011..0.011 rows=0 loops=1)
                     Index Cond: (last_modifier = 21321)
 Planning time: 0.704 ms
 Execution time: 0.153 ms

ऐसा लगता है कि हम एक साधारण पार्सर बना सकते हैं जो इस तरह से सभी सबसेक्ट्स को ढूंढता है और फिर से लिखता है, और देशी क्वेरी में हेरफेर करने के लिए इसे हाइबरनेट हुक में जोड़ता है।


यह मजेदार लगता है। क्या यह आसान नहीं है SELECTकि आप सभी प्रश्नों को हटा दें , जैसे कि प्रश्न में आपकी पहली क्वेरी है?
dezso

बेशक, मैं एक दो-चरण दृष्टिकोण कर सकता था: एक SELECTअलग से करो, और फिर बाहरी चयन के बाद एक स्थिर सूची के साथ करो IN। हालाँकि, यह काफी धीमा है (यदि सब-वेरीज़ कुछ परिणाम से अधिक है तो 5-10 बार), जैसा कि आपके पास अतिरिक्त नेटवर्क राउंड-ट्रिप है और आपके पास बहुत सारे परिणाम प्रारूपित हैं और फिर जावा उन परिणामों को पार्स कर रहा है (और फिर कर रहा है) वही फिर से पीछे की तरफ)। ऊपर दिए गए समाधान वही शब्दार्थिक रूप से करते हैं, जबकि पोस्टग्रेज के अंदर प्रक्रिया छोड़ते हैं। कुल मिलाकर, वर्तमान में यह हमारे मामले में सबसे छोटे संशोधन के साथ सबसे तेज़ तरीका है।
पी। सेपर

ओह समझा। मुझे नहीं पता था कि आप एक बार में कई आईडी प्राप्त कर सकते हैं।
dezso

1

एक दूसरे प्रश्न का उत्तर: हां, आप अपने उपनगर में ORDER BY जोड़ सकते हैं, जिसका सकारात्मक प्रभाव पड़ेगा। लेकिन यह प्रदर्शन में "EXISTS (उपश्रेणी)" समाधान के लिए simillar है। दो पंक्तियों के परिणामस्वरूप उपशम के साथ भी एक महत्वपूर्ण अंतर है।

SELECT mtid FROM publication
WHERE mtid IN (SELECT #column# ORDER BY #column#) OR last_modifier=21321
LIMIT 5000;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.