PostgreSQL
 sql >> डेटाबेस >  >> RDS >> PostgreSQL

अभिव्यक्ति सूचकांकों की उपयोगिता पर

मूल बातें और उन्नत विषयों दोनों पर PostgreSQL प्रशिक्षण पढ़ाते समय, मुझे अक्सर पता चलता है कि उपस्थित लोगों को बहुत कम पता है कि अभिव्यक्ति अनुक्रमणिका कितनी शक्तिशाली हो सकती है (यदि वे उनके बारे में बिल्कुल भी जानते हैं)। तो मैं आपको एक संक्षिप्त अवलोकन देता हूं।

तो, मान लें कि हमारे पास टाइमस्टैम्प की एक श्रृंखला के साथ एक टेबल है (हां, हमारे पास जेनरेट_सीरीज फ़ंक्शन है जो तिथियां उत्पन्न कर सकता है):

CREATE TABLE t AS
SELECT d, repeat(md5(d::text), 10) AS padding
  FROM generate_series(timestamp '1900-01-01',
                       timestamp '2100-01-01',
                       interval '1 day') s(d);
VACUUM ANALYZE t;

तालिका को थोड़ा बड़ा करने के लिए, इसमें एक पैडिंग कॉलम भी शामिल है। अब, तालिका में शामिल ~200 वर्षों में से केवल एक महीने का चयन करते हुए, एक साधारण श्रेणी क्वेरी करते हैं। अगर आप क्वेरी के बारे में बताते हैं, तो आपको कुछ इस तरह दिखाई देगा:

'2001-01-01' और '2001-02-01' के बीच में से
EXPLAIN SELECT * FROM t WHERE d BETWEEN '2001-01-01' AND '2001-02-01';

                               QUERY PLAN
------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..4416.75 rows=32 width=332)
   Filter: ((d >= '2001-01-01 00:00:00'::timestamp without time zone)
        AND (d <= '2001-02-01 00:00:00'::timestamp without time zone))
(2 rows)

और मेरे लैपटॉप पर, यह ~20ms में चलता है। बुरा नहीं है, इस पर विचार करते हुए ~75k पंक्तियों के साथ पूरी तालिका में चलना होगा।

लेकिन चलिए टाइमस्टैम्प कॉलम पर एक इंडेक्स बनाते हैं (यहां सभी इंडेक्स डिफ़ॉल्ट प्रकार हैं, यानी btree, जब तक कि स्पष्ट रूप से उल्लेख न किया गया हो):

CREATE INDEX idx_t_d ON t (d);

और अब क्वेरी को फिर से चलाने का प्रयास करते हैं:

                               QUERY PLAN
------------------------------------------------------------------------
 Index Scan using idx_t_d on t  (cost=0.29..9.97 rows=34 width=332)
   Index Cond: ((d >= '2001-01-01 00:00:00'::timestamp without time zone)
            AND (d <= '2001-02-01 00:00:00'::timestamp without time zone))
(2 rows)

और यह 0.5ms में चलता है, इसलिए लगभग 40x तेज। लेकिन यह निश्चित रूप से एक साधारण इंडेक्स था, जो सीधे कॉलम पर बनाया गया था, न कि एक्सप्रेशन इंडेक्स। तो चलिए मान लेते हैं कि हमें इस तरह की क्वेरी करते हुए हर महीने के प्रत्येक पहले दिन से डेटा का चयन करने की आवश्यकता है

SELECT * FROM t WHERE EXTRACT(day FROM d) = 1;

जो हालांकि सूचकांक का उपयोग नहीं कर सकता है, क्योंकि इसे स्तंभ पर एक अभिव्यक्ति का मूल्यांकन करने की आवश्यकता होती है, जबकि सूचकांक स्तंभ पर ही बनाया जाता है, जैसा कि EXPLAIN ANALYZE पर दिखाया गया है:

                               QUERY PLAN
------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..4416.75 rows=365 width=332)
                (actual time=0.045..40.601 rows=2401 loops=1)
   Filter: (date_part('day'::text, d) = '1'::double precision)
   Rows Removed by Filter: 70649
 Planning time: 0.209 ms
 Execution time: 43.018 ms
(5 rows)

इसलिए न केवल इसे अनुक्रमिक स्कैन करना होता है, बल्कि मूल्यांकन भी करना होता है, जिससे क्वेरी की अवधि 43ms तक बढ़ जाती है।

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

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

सिद्धांत रूप में डेटाबेस स्थिति को सीमा स्थितियों में बदलने की कोशिश कर सकता है, लेकिन यह अभिव्यक्ति के लिए अत्यंत कठिन और विशिष्ट है। इस मामले में हमें ऐसी "प्रति-दिन" श्रेणियों की एक अनंत संख्या उत्पन्न करनी होगी, क्योंकि योजनाकार वास्तव में तालिका में न्यूनतम/अधिकतम टाइमस्टैम्प नहीं जानता है। तो डेटाबेस कोशिश भी नहीं करता।

लेकिन जबकि डेटाबेस यह नहीं जानता कि परिस्थितियों को कैसे बदला जाए, डेवलपर्स अक्सर ऐसा करते हैं। उदाहरण के लिए

. जैसी शर्तों के साथ
(column + 1) >= 1000

इसे इस तरह फिर से लिखना मुश्किल नहीं है

column >= (1000 - 1)

जो इंडेक्स के साथ ठीक काम करता है।

लेकिन क्या होगा यदि ऐसा परिवर्तन संभव नहीं है, उदाहरण के लिए उदाहरण क्वेरी के लिए

SELECT * FROM t WHERE EXTRACT(day FROM d) = 1;

इस मामले में डेवलपर को d कॉलम के लिए अज्ञात न्यूनतम/अधिकतम के साथ एक ही समस्या का सामना करना पड़ेगा, और फिर भी यह बहुत सारी श्रेणियां उत्पन्न करेगा।

खैर, यह ब्लॉग पोस्ट एक्सप्रेशन इंडेक्स के बारे में है, और अब तक हमने केवल नियमित इंडेक्स का उपयोग किया है, जो सीधे कॉलम पर बनाया गया है। तो, चलिए पहला एक्सप्रेशन इंडेक्स बनाते हैं:

CREATE INDEX idx_t_expr ON t ((extract(day FROM d)));
ANALYZE t;

जो तब हमें यह व्याख्या योजना देता है

                               QUERY PLAN
------------------------------------------------------------------------
 Bitmap Heap Scan on t  (cost=47.35..3305.25 rows=2459 width=332)
                        (actual time=2.400..12.539 rows=2401 loops=1)
   Recheck Cond: (date_part('day'::text, d) = '1'::double precision)
   Heap Blocks: exact=2401
   ->  Bitmap Index Scan on idx_t_expr  (cost=0.00..46.73 rows=2459 width=0)
                                (actual time=1.243..1.243 rows=2401 loops=1)
         Index Cond: (date_part('day'::text, d) = '1'::double precision)
 Planning time: 0.374 ms
 Execution time: 17.136 ms
(7 rows)

इसलिए जबकि यह हमें पहले उदाहरण में इंडेक्स के समान 40x स्पीडअप नहीं देता है, यह थोड़े अपेक्षित है क्योंकि यह क्वेरी कहीं अधिक टुपल्स (2401 बनाम 32) लौटाती है। इसके अलावा वे पूरी तालिका में फैले हुए हैं और पहले उदाहरण की तरह स्थानीयकृत नहीं हैं। तो यह एक अच्छा 2x स्पीडअप है, और कई वास्तविक दुनिया के मामलों में आपको बहुत बड़े सुधार दिखाई देंगे।

लेकिन जटिल अभिव्यक्तियों वाली स्थितियों के लिए इंडेक्स का उपयोग करने की क्षमता यहां सबसे दिलचस्प जानकारी नहीं है - यही कारण है कि लोग अभिव्यक्ति इंडेक्स बनाते हैं। लेकिन यही एकमात्र लाभ नहीं है।

यदि आप ऊपर प्रस्तुत दो व्याख्या योजनाओं (बिना और अभिव्यक्ति सूचकांक के) को देखते हैं, तो आप इसे देख सकते हैं:

                               QUERY PLAN
------------------------------------------------------------------------
 Seq Scan on t  (cost=0.00..4416.75 rows=365 width=332)
                (actual time=0.045..40.601 rows=2401 loops=1)
 ...
                               QUERY PLAN
------------------------------------------------------------------------
 Bitmap Heap Scan on t  (cost=47.35..3305.25 rows=2459 width=332)
                        (actual time=2.400..12.539 rows=2401 loops=1)
 ...

राइट - एक्सप्रेशन इंडेक्स बनाने से अनुमानों में काफी सुधार हुआ है। सूचकांक के बिना हमारे पास कच्चे टेबल कॉलम के लिए केवल आंकड़े (एमसीवी + हिस्टोग्राम) हैं, इसलिए डेटाबेस को पता नहीं है कि अभिव्यक्ति का अनुमान कैसे लगाया जाए

EXTRACT(day FROM d) = 1

इसलिए यह इसके बजाय समानता की स्थिति के लिए एक डिफ़ॉल्ट अनुमान लागू करता है, जो सभी पंक्तियों का 0.5% है - क्योंकि तालिका में 73050 पंक्तियाँ हैं, हम केवल 365 पंक्तियों के अनुमान के साथ समाप्त होते हैं। वास्तविक दुनिया के अनुप्रयोगों में बहुत खराब अनुमान त्रुटियों को देखना आम बात है।

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

यह एक बहुत बड़ा लाभ है, और गलत अनुमानों के कारण खराब क्वेरी योजनाओं के कुछ मामलों को ठीक करने में मदद कर सकता है। फिर भी अधिकांश लोग इस उपयोगी उपकरण से अनजान हैं।

और इस टूल की उपयोगिता केवल 9.4 में JSONB डेटा प्रकार की शुरुआत के साथ बढ़ी, क्योंकि यह JSONB दस्तावेज़ों की सामग्री के बारे में आंकड़े एकत्र करने का एकमात्र तरीका है।

JSONB दस्तावेज़ों को अनुक्रमित करते समय, दो बुनियादी अनुक्रमण रणनीतियाँ मौजूद होती हैं। आप या तो पूरे दस्तावेज़ पर एक GIN/GiST अनुक्रमणिका बना सकते हैं, उदा। इस तरह

CREATE INDEX ON t USING GIN (jsonb_column);

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

उदाहरण के लिए एक्सप्रेशन इंडेक्स इस तरह बनाए गए:

CREATE INDEX ON t ((jsonb_column->'id'));

केवल विशेष अभिव्यक्ति के लिए उपयोगी होगा, अर्थात यह नव निर्मित अनुक्रमणिका

. के लिए उपयोगी होगी
SELECT * FROM t WHERE jsonb_column ->> 'id' = 123;

लेकिन अन्य JSON कुंजियों तक पहुँचने वाले प्रश्नों के लिए नहीं, जैसे 'मान' उदाहरण के लिए

SELECT * FROM t WHERE jsonb_column ->> 'value' = 'xxxx';

यह कहना नहीं है कि पूरे दस्तावेज़ पर GIN/GiST इंडेक्स बेकार हैं, लेकिन आपको चुनना होगा। या तो आप एक केंद्रित अभिव्यक्ति सूचकांक बनाते हैं, जो किसी विशेष कुंजी को क्वेरी करते समय उपयोगी होता है और अभिव्यक्ति पर आंकड़ों के अतिरिक्त लाभ के साथ। या आप पूरे दस्तावेज़ पर एक GIN/GiST अनुक्रमणिका बनाते हैं, जो मनमानी कुंजियों पर प्रश्नों को संभालने में सक्षम है, लेकिन आंकड़ों के बिना।

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

अफसोस की बात है कि आप पूरा केक नहीं खा सकते, क्योंकि एक्सप्रेशन इंडेक्स और GIN/GiST इंडेक्स अलग-अलग स्थितियों का इस्तेमाल करते हैं

-- expression (btree)
SELECT * FROM t WHERE jsonb_column ->> 'id' = 123;

-- GIN/GiST
SELECT * FROM t WHERE jsonb_column @> '{"id" : 123}';

इसलिए योजनाकार एक ही समय में उनका उपयोग नहीं कर सकता - अनुमान के लिए अभिव्यक्ति अनुक्रमणिका और निष्पादन के लिए GIN/GiST।


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. इंडेक्स स्कैन ज्यादा बेहतर विकल्प होने पर इंडेक्स का उपयोग नहीं करना पोस्टग्रेस

  2. एसक्यूएल स्टेटमेंट एरर:कॉलम .. मौजूद नहीं है

  3. PostgreSQL:मल्टी-कॉलम अद्वितीय बाधा के आधार पर ऑटो-इंक्रीमेंट

  4. PHP और पोस्टग्रेस:​​त्रुटियों को पकड़ना?

  5. Postgres 9.1 में किसी तालिका का OID निर्धारित करना?