PostgreSQL में तेजी से DISTINCT कैसे करें?


13

मेरे पास station_logsPostgreSQL 9.6 डेटाबेस में एक तालिका है:

    Column     |            Type             |    
---------------+-----------------------------+
 id            | bigint                      | bigserial
 station_id    | integer                     | not null
 submitted_at  | timestamp without time zone | 
 level_sensor  | double precision            | 
Indexes:
    "station_logs_pkey" PRIMARY KEY, btree (id)
    "uniq_sid_sat" UNIQUE CONSTRAINT, btree (station_id, submitted_at)

मैं प्रत्येक के लिए अंतिम level_sensorमूल्य के आधार पर पाने की कोशिश कर रहा हूं । लगभग 400 अद्वितीय मूल्य हैं, और प्रति दिन लगभग 20k पंक्तियाँ हैं ।submitted_atstation_idstation_idstation_id

सूचकांक बनाने से पहले:

EXPLAIN ANALYZE
SELECT DISTINCT ON(station_id) station_id, submitted_at, level_sensor
FROM station_logs ORDER BY station_id, submitted_at DESC;
 अद्वितीय (लागत = 4347852.14..4450301.72 पंक्तियाँ = 89 चौड़ाई = 20) (वास्तविक समय = 22202.080..27619.167 पंक्तियाँ = 98 छोर = 1)
   -> सॉर्ट (लागत = 4347852.14..4399076.93 पंक्तियों = 20489916 चौड़ाई = 20) (वास्तविक समय = 22202.077..26540.827 पंक्तियों = 20489812 छोरों = 1)
         सॉर्ट कुंजी: स्टेशन_आईडी, सबमिट किया गया डीएटीसी
         सॉर्ट विधि: बाहरी मर्ज डिस्क: 681040kB
         -> स्टेशन_लॉग्स पर सीक स्कैन (लागत = 0.00..598895.16 पंक्तियों = 20489916 चौड़ाई = 20) (वास्तविक समय = 0.023..3443.587 पंक्तियों = 20489812 छोरों = $
 योजना समय: 0.072 एमएस
 निष्पादन समय: 27690.644 एमएस

सूचकांक बनाना:

CREATE INDEX station_id__submitted_at ON station_logs(station_id, submitted_at DESC);

इंडेक्स बनाने के बाद, उसी क्वेरी के लिए:

 अद्वितीय (लागत = 0.56..2156367.51 पंक्तियाँ = 89 चौड़ाई = 20) (वास्तविक समय = 0.184..16263.413 पंक्तियाँ = 98 छोर = 1)
   -> स्टेशन_लॉग्स पर स्टेशन_id__submitted_at (लागत = 0.56..2105142.98 पंक्तियों = 20489812 चौड़ाई = 20) का उपयोग करके सूचकांक स्कैन करें (वास्तविक समय = 0.181..1 $
 योजना समय: 0.206 एमएस
 निष्पादन समय: 16263.490 एमएस

क्या इस क्वेरी को तेज़ बनाने का कोई तरीका है? उदाहरण के लिए 1 सेकंड की तरह, 16 सेकंड अभी भी बहुत अधिक है।


2
कितने अलग स्टेशन आईडी हैं, यानी क्वेरी कितनी पंक्तियों में वापस आती है? और Postgres का कौन सा संस्करण?
ypercube y

पोस्टग्रे 9.6, लगभग 400 अद्वितीय स्टेशन_एड, और स्टेशन के अनुसार प्रति दिन लगभग 20k रिकॉर्ड
कोकिज़ु

इस क्वेरी देता है एक "submitted_at के आधार पर पिछले level_sensor मूल्य प्रत्येक station_id के लिए,"। जिन मामलों में आपको इसकी आवश्यकता नहीं है, उन्हें छोड़कर एक यादृच्छिक विकल्प शामिल है।
फिलीपिक्सी

जवाबों:


18

केवल 400 स्टेशनों के लिए, यह क्वेरी बड़े पैमाने पर तेज़ होगी :

SELECT s.station_id, l.submitted_at, l.level_sensor
FROM   station s
CROSS  JOIN LATERAL (
   SELECT submitted_at, level_sensor
   FROM   station_logs
   WHERE  station_id = s.station_id
   ORDER  BY submitted_at DESC NULLS LAST
   LIMIT  1
   ) l;

यहाँ dbfiddle
(इस क्वेरी के लिए योजनाओं की तुलना, एबेलिस्टो के विकल्प और आपके मूल)

EXPLAIN ANALYZEओपी द्वारा प्रदान किए गए परिणाम :

 नेस्टेड लूप (लागत = 0.56..356.65 पंक्तियाँ = 102 चौड़ाई = 20) (वास्तविक समय = 0.034..0.979 पंक्तियाँ = 98 छोर = 1)
   -> स्टेशनों पर एसईसी स्कैन (लागत = 0.00..3.02 पंक्तियों = 102 चौड़ाई = 4) (वास्तविक समय = 0.009..0.016 पंक्तियों = 102 छोरों = 1)
   -> सीमा (लागत = 0.56..3.45 पंक्तियाँ = 1 चौड़ाई = 16) (वास्तविक समय = 0.009..0.009 पंक्तियाँ = 1 छोर = 102)
         -> स्टेशन_लॉग्स (लागत = 0.56..664062.38 पंक्तियों = 230223 चौड़ाई = 16) पर स्टेशन_id__submitted_at का उपयोग करके सूचकांक स्कैन करें (वास्तविक समय = 0.009 $
               सूचकांक कंडोम: (station_id = s.id)
 योजना समय: 0.542 एमएस
 निष्पादन समय: 1.013 एमएस   - !!

आपके द्वारा आवश्यक एकमात्र इंडेक्स आपके द्वारा बनाया गया है station_id__submitted_at:। UNIQUEबाधा uniq_sid_satभी काम करता है, मूल रूप से। दोनों को बनाए रखना डिस्क स्थान की बर्बादी और प्रदर्शन लिखना जैसा लगता है।

मैं जोड़ा NULLS LASTकरने के लिए ORDER BYक्योंकि क्वेरी में submitted_atपरिभाषित नहीं है NOT NULL। आदर्श रूप से, यदि लागू हो!, NOT NULLकॉलम में एक बाधा जोड़ें submitted_at, अतिरिक्त सूचकांक को छोड़ दें और NULLS LASTक्वेरी से हटा दें ।

यदि submitted_atहो सकता है NULL, तो UNIQUEअपने वर्तमान सूचकांक और अद्वितीय बाधा दोनों को बदलने के लिए इस सूचकांक को बनाएं :

CREATE UNIQUE INDEX station_logs_uni ON station_logs(station_id, submitted_at DESC NULLS LAST);

विचार करें:

यह प्रासंगिक (आमतौर पर PK) प्रति एक पंक्ति के साथ एक अलग तालिकाstation मान रहा है station_id- जो आपके पास होना चाहिए। यदि आपके पास यह नहीं है, तो इसे बनाएं। फिर, इस rCTE तकनीक के साथ बहुत तेजी से:

CREATE TABLE station AS
WITH RECURSIVE cte AS (
   (
   SELECT station_id
   FROM   station_logs
   ORDER  BY station_id
   LIMIT  1
   )
   UNION ALL
   SELECT l.station_id
   FROM   cte c
   ,      LATERAL (   
      SELECT station_id
      FROM   station_logs
      WHERE  station_id > c.station_id
      ORDER  BY station_id
      LIMIT  1
      ) l
   )
TABLE cte;

मैं फिडल में भी इसका इस्तेमाल करता हूं। आप अपने कार्य को हल करने के लिए एक समान क्वेरी का उपयोग कर सकते हैं, बिना stationटेबल के - यदि आप इसे बनाने के लिए आश्वस्त नहीं हो सकते हैं।

विस्तृत निर्देश, स्पष्टीकरण और विकल्प:

अनुक्रमणिका का अनुकूलन करें

आपकी क्वेरी अब बहुत तेज़ होनी चाहिए। यदि आपको अभी भी पठन प्रदर्शन का अनुकूलन करने की आवश्यकता है ...

इंडेक्स-ओनली स्कैन कीlevel_sensor अनुमति देने के लिए इंडेक्स के अंतिम कॉलम के रूप में जोड़ना समझ में आता है , जैसे कि जोनलो ने टिप्पणी की थीCon: यह इंडेक्स को बड़ा बनाता है - जो इसे उपयोग करने वाले सभी प्रश्नों के लिए थोड़ी लागत जोड़ता है। प्रो: यदि आपको वास्तव में इंडेक्स केवल स्कैन से मिलता है, तो हाथ में क्वेरी को ढेर पृष्ठों पर नहीं जाना पड़ता है, जो इसे लगभग दो बार उपवास करता है। लेकिन यह बहुत तेज क्वेरी के लिए एक अबाधित लाभ हो सकता है।

हालाँकि , मैं आपके मामले के लिए काम करने की उम्मीद नहीं करता। आपने उल्लिखित किया था:

... प्रति दिन लगभग 20k पंक्तियाँ station_id

आमतौर पर, यह रिलीजिंग लोड को इंगित करेगा ( station_idप्रत्येक 5 सेकंड में 1 )। और आप नवीनतम पंक्ति में रुचि रखते हैं । सूचकांक-केवल स्कैन हीप पृष्ठों के लिए काम करता है जो सभी लेनदेन के लिए दिखाई देते हैं (दृश्यता मानचित्र में बिट सेट है)। आपको VACUUMलिखने के भार के साथ रखने के लिए तालिका के लिए बेहद आक्रामक सेटिंग्स को चलाना होगा , और यह अभी भी अधिकांश समय काम नहीं करेगा। अगर मेरी धारणा सही है, तो इंडेक्स-ओनली स्कैन बाहर हैं, इंडेक्स में जोड़ें level_sensor

OTOH, अगर मेरी धारणाएं पकड़ में हैं और आपकी तालिका बहुत बड़ी हो रही है , तो BRIN इंडेक्स मदद कर सकता है। सम्बंधित:

या, और भी अधिक विशिष्ट और अधिक कुशल: अप्रासंगिक पंक्तियों के थोक में कटौती करने के लिए केवल नवीनतम परिवर्धन के लिए एक आंशिक सूचकांक:

CREATE INDEX station_id__submitted_at_recent_idx ON station_logs(station_id, submitted_at DESC NULLS LAST)
WHERE submitted_at > '2017-06-24 00:00';

एक टाइमस्टैम्प का चयन करें जिसके लिए आप जानते हैं कि छोटी पंक्तियों का अस्तित्व होना चाहिए। आपको WHEREसभी प्रश्नों के लिए एक मेल शर्त जोड़ना होगा , जैसे:

...
WHERE  station_id = s.station_id
AND    submitted_at > '2017-06-24 00:00'
...

आपको समय-समय पर सूचकांक और क्वेरी को अनुकूलित करना होगा।
अधिक विवरण के साथ संबंधित जवाब:


कभी भी मुझे पता है कि मैं एक नेस्टेड लूप (अक्सर) चाहता हूं, LATERAL का उपयोग कई स्थितियों के लिए एक प्रदर्शन को बढ़ावा देने वाला है।
पॉल ड्रेपर

6

क्लासिक तरीका आज़माएं:

create index idx_station_logs__station_id on station_logs(station_id);
create index idx_station_logs__submitted_at on station_logs(submitted_at);

analyse station_logs;

with t as (
  select station_id, max(submitted_at) submitted_at 
  from station_logs 
  group by station_id)
select * 
from t join station_logs l on (
  l.station_id = t.station_id and l.submitted_at = t.submitted_at);

dbfiddle

ThreadStarter द्वारा विश्लेषण का विस्तार करें

 Nested Loop  (cost=701344.63..702110.58 rows=4 width=155) (actual time=6253.062..6253.544 rows=98 loops=1)
   CTE t
     ->  HashAggregate  (cost=701343.18..701344.07 rows=89 width=12) (actual time=6253.042..6253.069 rows=98 loops=1)
           Group Key: station_logs.station_id
           ->  Seq Scan on station_logs  (cost=0.00..598894.12 rows=20489812 width=12) (actual time=0.034..1841.848 rows=20489812 loop$
   ->  CTE Scan on t  (cost=0.00..1.78 rows=89 width=12) (actual time=6253.047..6253.085 rows=98 loops=1)
   ->  Index Scan using station_id__submitted_at on station_logs l  (cost=0.56..8.58 rows=1 width=143) (actual time=0.004..0.004 rows=$
         Index Cond: ((station_id = t.station_id) AND (submitted_at = t.submitted_at))
 Planning time: 0.542 ms
 Execution time: 6253.701 ms
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.