बेतहाशा गलत पंक्ति अनुमानों के कारण धीमी गति से पूर्ण खोज


10

इस डेटाबेस के विरुद्ध फुलटेक्स्ट क्वेश्चन (आरटी ( रिक्वेस्ट ट्रैकर ) टिकटों को स्टोर करके ) निष्पादित करने में बहुत लंबा समय लगता है। अटैचमेंट टेबल (फुलटेक्स्ट डेटा युक्त) लगभग 15GB है।

डेटाबेस स्कीमा निम्नानुसार है, यह लगभग 2 मिलियन पंक्तियाँ हैं:

rt4 = # \ d + अनुलग्नक
                                                    टेबल "public.attachments"
     स्तंभ | प्रकार | संशोधक | भंडारण | विवरण
----------------- + ----------------------------- + - -------------------------------------------------- ------ + ---------- + -------------
 आईडी | पूर्णांक | डिफॉल्ट नेक्स्ट नेवल नहीं ('अटैचमेंट्स_ड_सेक' :: regclass) | सादा |
 लेन-देन | पूर्णांक | अशक्त नहीं | सादा |
 माता पिता | पूर्णांक | डिफ़ॉल्ट नहीं 0 | सादा |
 संदेश देने वाला | चरित्र भिन्न (160) | | विस्तारित |
 विषय | वर्ण भिन्नता (255) | | विस्तारित |
 फ़ाइल नाम | वर्ण भिन्नता (255) | | विस्तारित |
 सामग्री चरित्र भिन्न (80) | | विस्तारित |
 सामग्री चरित्र भिन्न (80) | | विस्तारित |
 सामग्री | पाठ | | विस्तारित |
 हेडर | पाठ | | विस्तारित |
 निर्माता | पूर्णांक | डिफ़ॉल्ट नहीं 0 | सादा |
 बनाया | टाइम ज़ोन के बिना टाइमस्टैम्प | | सादा |
 contentindex | tsvector | | विस्तारित |
इंडेक्स:
    "संलग्नक_पैक" प्राथमिक कुंजी, बीटीआरआई (आईडी)
    "संलग्नक 1" बीटीआरवाई (मूल)
    "संलग्नक 2" बीटीआरआई (लेन-देन)
    "संलग्नक 3" बीटीआरवाई (मूल, लेन-देन)
    "contentindex_idx" जिन (contentindex)
OIDs है: नहीं

मैं इस पर डेटाबेस को बहुत तेज़ी से क्वेरी कर सकता हूं (<1s) जैसे कि क्वेरी के साथ:

select objectid
from attachments
join transactions on attachments.transactionid = transactions.id
where contentindex @@ to_tsquery('frobnicate');

हालाँकि, जब RT एक क्वेरी चलाता है जो एक ही टेबल पर एक फुलटेक्स इंडेक्स सर्च करने के लिए होती है, तो इसे पूरा होने में आमतौर पर सैकड़ों सेकंड लगते हैं। क्वेरी विश्लेषण आउटपुट निम्नानुसार है:

सवाल

SELECT COUNT(DISTINCT main.id)
FROM Tickets main
JOIN Transactions Transactions_1 ON ( Transactions_1.ObjectType = 'RT::Ticket' )
                                AND ( Transactions_1.ObjectId = main.id )
JOIN Attachments Attachments_2 ON ( Attachments_2.TransactionId = Transactions_1.id )
WHERE (main.Status != 'deleted')
AND ( ( ( Attachments_2.ContentIndex @@ plainto_tsquery('frobnicate') ) ) )
AND (main.Type = 'ticket')
AND (main.EffectiveId = main.id);

EXPLAIN ANALYZE उत्पादन

                                                                             जल्दी योजना 
-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- --------------
 सकल (लागत = 51210.60..51210.61 पंक्तियाँ = 1 चौड़ाई = 4) (वास्तविक समय = 477778.806..477778.806 पंक्तियाँ = 1 छोर = 1)
   -> नेस्टेड लूप (लागत = 0.00..51210.57 पंक्तियाँ = 15 चौड़ाई = 4) (वास्तविक समय = 17943.986..477775.174 पंक्तियाँ = 4197 छोर = 1)
         -> नेस्टेड लूप (लागत = 0.00..40643.08 पंक्तियाँ = 6507 चौड़ाई = 8) (वास्तविक समय = 8.526..20610.380 पंक्तियाँ = 1714818 लूप = 1)
               -> टिकट मुख्य पर स्कैन स्कैन (लागत = 0.00..9818.37 पंक्तियों = 598 चौड़ाई = 8) (वास्तविक समय = 0.008..256.042 पंक्तियों = 96990 छोरों = 1)
                     फ़िल्टर: (((स्टेटस) :: टेक्स्ट 'डिलीट' :: टेक्स्ट) और (आईडी = प्रभावी) और (टाइप) :: टेक्स्ट = 'टिकट' :: टेक्स्ट)
               -> लेनदेन लेनदेन 1 पर लेनदेन 1 का उपयोग करके सूचकांक स्कैन करें (लागत = 0.00..51.36 पंक्तियों = 15 चौड़ाई = 8) (वास्तविक समय = 0.102..0.202 पंक्तियों = 18 छोरों = 96990)
                     सूचकांक कंडोम: (((ऑब्जेक्टिव) :: टेक्स्ट = 'आरटी :: टिकट' :: टेक्स्ट) और (ऑब्जेक्टिड = मेन.ड)
         -> अनुलग्‍नकों पर अनुलग्नकों 2 का उपयोग कर सूचकांक स्कैन करें। संलग्नक_2 (लागत = 0.00..1.61 पंक्तियाँ = 1 चौड़ाई = 4) (वास्तविक समय = 0.266..0.266 पंक्तियाँ = 0 छोर = 1714818)
               सूचकांक कंडोम: (लेन-देन = लेनदेन_1.id)
               फ़िल्टर: (contentindex @@ plainto_tsquery ('फ्रोबनिकेट' :: टेक्स्ट))
 कुल रनटाइम: 477778.883 एमएस

जहां तक ​​मैं बता सकता हूं, मुद्दा यह प्रतीत होता है कि यह contentindexफ़ील्ड पर बनाए गए सूचकांक ( contentindex_idx) का उपयोग नहीं कर रहा है , बल्कि यह संलग्नक तालिका में बड़ी संख्या में मिलान पंक्तियों पर एक फिल्टर कर रहा है। व्याख्या आउटपुट में पंक्ति गिना जाना भी एक गलत ANALYZEअनुमान है, भले ही हाल ही में : अनुमानित पंक्तियों = 6507 वास्तविक पंक्तियों = 1714818।

मुझे यकीन नहीं है कि इसके साथ आगे कहां जाना है।


अपग्रेड करने से अतिरिक्त लाभ मिलेगा। बहुत सारे सामान्य सुधारों के अलावा , विशेष रूप से: 9.2 सूचकांक-केवल स्कैन और स्केलेबिलिटी में सुधार की अनुमति देता है। आगामी 9.4 जीआईएन इंडेक्स के लिए बड़ी वृद्धि लाने जा रहा है।
इरविन ब्रान्डसेट्टर 16

जवाबों:


5

यह एक हजार और एक तरीके से सुधारा जा सकता है, फिर यह मिलीसेकंड का मामला होना चाहिए ।

बेहतर प्रश्न

यह केवल उपनामों के साथ सुधारित आपकी क्वेरी है और कोहरे को हटाने के लिए हटाए गए कुछ शोर:

SELECT count(DISTINCT t.id)
FROM   tickets      t
JOIN   transactions tr ON tr.objectid = t.id
JOIN   attachments  a  ON a.transactionid = tr.id
WHERE  t.status <> 'deleted'
AND    t.type = 'ticket'
AND    t.effectiveid = t.id
AND    tr.objecttype = 'RT::Ticket'
AND    a.contentindex @@ plainto_tsquery('frobnicate');

आपकी क्वेरी की अधिकांश समस्या पहले दो तालिकाओं में निहित है ticketsऔर transactions, जो प्रश्न से गायब हैं। मैं शिक्षित अनुमानों से भर रहा हूं।

  • t.status, t.objecttypeऔर tr.objecttypeशायद नहीं होना चाहिए text, लेकिन enumया संभवतः कुछ बहुत ही छोटे मूल्य एक लुक-अप तालिका संदर्भित।

EXISTS अर्द्ध में शामिल होने के

मान लिया जाये कि tickets.idप्राथमिक कुंजी है, यह फिर से लिखा प्रपत्र बहुत सस्ता होना चाहिए:

SELECT count(*)
FROM   tickets t
WHERE  status <> 'deleted'
AND    type = 'ticket'
AND    effectiveid = id
AND    EXISTS (
   SELECT 1
   FROM   transactions tr
   JOIN   attachments  a ON a.transactionid = tr.id
   WHERE  tr.objectid = t.id
   AND    tr.objecttype = 'RT::Ticket'
   AND    a.contentindex @@ plainto_tsquery('frobnicate')
   );

इसके बजाय पंक्तियों को दो 1: n के साथ गुणा करने के बजाय, केवल अंत में कई मैचों को समाप्त करने के लिए count(DISTINCT id), एक EXISTSअर्ध-जुड़ाव का उपयोग करें , जो पहले मैच के मिलते ही आगे देखना बंद कर सकता है और उसी समय अंतिम DISTINCTचरण का पालन करता है। प्रति प्रलेखन:

उपश्रेणी आमतौर पर केवल यह निर्धारित करने के लिए पर्याप्त रूप से निष्पादित की जाएगी कि कम से कम एक पंक्ति वापस आ गई है, पूरा करने के लिए सभी तरह से नहीं।

प्रभावशीलता इस बात पर निर्भर करती है कि प्रति टिकट कितने लेन-देन और प्रति लेनदेन संलग्न हैं।

के साथ जुड़ने का क्रम निर्धारित करें join_collapse_limit

आप तो जानते हैं कि के लिए अपना खोज शब्द attachments.contentindexहै बहुत चयनात्मक - क्वेरी में अन्य शर्तों की तुलना में अधिक चयनात्मक (जो शायद नहीं, बल्कि 'समस्या' के लिए 'frobnicate' के लिए मामला है), तो आप जुड़ जाता है के अनुक्रम मजबूर कर सकते हैं। क्वेरी प्लानर विशेष रूप से विशेष शब्दों की चयनात्मकता का न्याय कर सकता है, सबसे आम लोगों को छोड़कर। प्रति प्रलेखन:

join_collapse_limit( integer)

[...]
क्योंकि क्वेरी प्लानर हमेशा इष्टतम जुड़ाव क्रम का चयन नहीं करता है, इसलिए उन्नत उपयोगकर्ता अस्थायी रूप से इस चर को 1 में सेट करने के लिए चुनाव कर सकते हैं, और फिर उस जुड़ने वाले आदेश को स्पष्ट रूप से निर्दिष्ट कर सकते हैं।

SET LOCALकेवल वर्तमान लेनदेन के लिए इसे सेट करने के उद्देश्य के लिए उपयोग करें ।

BEGIN;
SET LOCAL join_collapse_limit = 1;

SELECT count(DISTINCT t.id)
FROM   attachments  a                              -- 1st
JOIN   transactions tr ON tr.id = a.transactionid  -- 2nd
JOIN   tickets      t  ON t.id = tr.objectid       -- 3rd
WHERE  t.status <> 'deleted'
AND    t.type = 'ticket'
AND    t.effectiveid = t.id
AND    tr.objecttype = 'RT::Ticket'
AND    a.contentindex @@ plainto_tsquery('frobnicate');

ROLLBACK; -- or COMMIT;

WHEREपरिस्थितियों का क्रम हमेशा अप्रासंगिक होता है। केवल जोड़ का क्रम यहां प्रासंगिक है।

या @ विकल्प की तरह एक सीटीई का उपयोग करें "विकल्प 2" में बताते हैं। एक समान प्रभाव के लिए।

इंडेक्स

बी-ट्री इंडेक्स

सभी शर्तों पर ले ticketsकि अधिकांश क्वेरी के साथ अभिन्न रूप से उपयोग किया जाता है और एक बनाने आंशिक सूचकांक पर tickets:

CREATE INDEX tickets_partial_idx
ON tickets(id)
WHERE  status <> 'deleted'
AND    type = 'ticket'
AND    effectiveid = id;

यदि कोई एक स्थिति परिवर्तनशील है, तो उसे WHEREस्थिति से हटा दें और स्तंभ को अनुक्रमणिका स्तंभ के रूप में प्रस्तुत करें।

एक और एक पर transactions:

CREATE INDEX transactions_partial_idx
ON transactions(objecttype, objectid, id)

तीसरा कॉलम सिर्फ इंडेक्स-ओनली स्कैन को सक्षम करने के लिए है।

इसके अलावा, चूंकि आपके पास दो पूर्णांक स्तंभों के साथ यह समग्र सूचकांक है attachments:

"attachments3" btree (parent, transactionid)

यह अतिरिक्त सूचकांक एक पूर्ण अपशिष्ट है , इसे हटा दें:

"attachments1" btree (parent)

विवरण:

GIN सूचकांक

transactionidइसे और अधिक प्रभावी बनाने के लिए अपने GIN इंडेक्स में जोड़ें । यह एक और चांदी की गोली हो सकती है , क्योंकि यह संभावित रूप से केवल-केवल स्कैन की अनुमति देता है, बड़ी तालिका की यात्राओं को पूरी तरह से समाप्त कर देता है ।
आपको अतिरिक्त मॉड्यूल द्वारा प्रदान की जाने वाली अतिरिक्त ऑपरेटर कक्षाएं चाहिए btree_gin। विस्तृत निर्देश:

"contentindex_idx" gin (transactionid, contentindex)

एक integerकॉलम से 4 बाइट्स इंडेक्स को बहुत बड़ा नहीं बनाते हैं। इसके अलावा, आपके लिए सौभाग्य से, GIN इंडेक्स एक महत्वपूर्ण पहलू में B- ट्री इंडेक्स से अलग हैं। प्रति प्रलेखन:

एक बहुउद्देशीय GIN सूचकांक का उपयोग क्वेरी स्थितियों के साथ किया जा सकता है जिसमें सूचकांक के कॉलम का कोई सबसेट शामिल होता है । B- ट्री या GiST के विपरीत, इंडेक्स सर्च इफ़ेक्ट वही होता है जो इंडेक्स कॉलम (s) क्वेरी शर्तों का उपयोग करता है।

बोल्ड जोर मेरा। तो आपको बस एक (बड़ा और कुछ महंगा) GIN इंडेक्स की आवश्यकता है।

तालिका परिभाषा

integer not null columnsसामने की ओर ले जाएँ । यह भंडारण और प्रदर्शन पर कुछ छोटे सकारात्मक प्रभाव डालता है। इस मामले में प्रति पंक्ति 4 से 8 बाइट बचाता है।

                      Table "public.attachments"
         Column      |            Type             |         Modifiers
    -----------------+-----------------------------+------------------------------
     id              | integer                     | not null default nextval('...
     transactionid   | integer                     | not null
     parent          | integer                     | not null default 0
     creator         | integer                     | not null default 0  -- !
     created         | timestamp                   |                     -- !
     messageid       | character varying(160)      |
     subject         | character varying(255)      |
     filename        | character varying(255)      |
     contenttype     | character varying(80)       |
     contentencoding | character varying(80)       |
     content         | text                        |
     headers         | text                        |
     contentindex    | tsvector                    |

3

विकल्प 1

योजनाकार को प्रभावी और आईडी के बीच संबंधों की वास्तविक प्रकृति के बारे में कोई जानकारी नहीं है, और इसलिए शायद यह सोचता है:

main.EffectiveId = main.id

यह वास्तव में है की तुलना में बहुत अधिक चयनात्मक होने जा रहा है। अगर ऐसा है तो मुझे लगता है कि यह है, प्रभावी लगभग हमेशा main.id के बराबर है, लेकिन योजनाकार को यह पता नहीं है।

इस प्रकार के संबंधों को संग्रहीत करने का संभवतः बेहतर तरीका आमतौर पर "प्रभावी रूप से आईडी के समान" का मतलब करने के लिए प्रभावी के NULL मूल्य को परिभाषित करना है, और इसमें अंतर होने पर ही कुछ संग्रहीत करना है।

यह मानते हुए कि आप अपने स्कीमा को पुनर्गठित नहीं करना चाहते हैं, आप इसे उस खंड के रूप में लिखकर प्राप्त करने का प्रयास कर सकते हैं:

main.EffectiveId+0 between main.id+0 and main.id+0

योजनाकार मान सकता है कि betweenयह एक समानता से कम चयनात्मक है, और यह इसके वर्तमान जाल से बाहर निकलने के लिए पर्याप्त हो सकता है।

विकल्प 2

CTE का उपयोग करने के लिए एक और तरीका है:

WITH attach as (
    SELECT * from Attachments 
        where ContentIndex @@ plainto_tsquery('frobnicate') 
)
<rest of query goes here, with 'attach' used in place of 'Attachments'>

यह प्लानर को ContentIndex को सेलेक्टिविटी के स्रोत के रूप में उपयोग करने के लिए मजबूर करता है। एक बार जब ऐसा करने के लिए मजबूर किया जाता है, तो टिकट तालिका पर भ्रामक कॉलम सहसंबंध अब उतना आकर्षक नहीं लगेगा। बेशक अगर कोई icate फ्रोबनिकेट ’के बजाय 'समस्या’ खोजता है, तो वह पीछे हट सकता है।

विकल्प 3

आगे की खराब पंक्ति के अनुमानों की जांच करने के लिए, आपको नीचे दिए गए क्वेरी को सभी 2 ^ 3 = 8 में अलग-अलग और खंडों के क्रमपरिवर्तन के बारे में बताना चाहिए। इससे यह पता लगाने में मदद मिलेगी कि खराब अनुमान कहां से आ रहा है।

explain analyze
SELECT * FROM Tickets main WHERE 
   main.Status != 'deleted' AND 
   main.Type = 'ticket' AND 
   main.EffectiveId = main.id;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.