बड़े PostgresSQL तालिका में COUNT / GROUP-BY के प्रदर्शन में सुधार?


24

मैं PostgresSQL 9.2 चला रहा हूं और लगभग 6,700,000 पंक्तियों के साथ 12 कॉलम का संबंध है। इसमें 3 डी स्पेस में नोड होते हैं, प्रत्येक एक उपयोगकर्ता को संदर्भित करता है (जिसने इसे बनाया है)। क्वेरी करने के लिए कि उपयोगकर्ता ने कितने नोड्स बनाए हैं जो मैं निम्नलिखित करता हूं ( explain analyzeअधिक जानकारी के लिए जोड़ा गया ):

EXPLAIN ANALYZE SELECT user_id, count(user_id) FROM treenode WHERE project_id=1 GROUP BY user_id;
                                                    QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=253668.70..253669.07 rows=37 width=8) (actual time=1747.620..1747.623 rows=38 loops=1)
   ->  Seq Scan on treenode  (cost=0.00..220278.79 rows=6677983 width=8) (actual time=0.019..886.803 rows=6677983 loops=1)
         Filter: (project_id = 1)
 Total runtime: 1747.653 ms

जैसा कि आप देख सकते हैं, यह लगभग 1.7 सेकंड लेता है। यह डेटा की मात्रा को देखते हुए बहुत बुरा नहीं है, लेकिन मुझे आश्चर्य है कि अगर इसमें सुधार किया जा सकता है। मैंने उपयोगकर्ता कॉलम पर BTree इंडेक्स जोड़ने की कोशिश की, लेकिन इससे किसी भी तरह से मदद नहीं मिली।

क्या आपके पास वैकल्पिक सुझाव हैं?


पूर्णता की खातिर, यह सभी सूचकांकों के साथ पूर्ण तालिका परिभाषा है (विदेशी कुंजी बाधाओं के बिना, संदर्भ और ट्रिगर):

    Column     |           Type           |                      Modifiers                    
---------------+--------------------------+------------------------------------------------------
 id            | bigint                   | not null default nextval('concept_id_seq'::regclass)
 user_id       | bigint                   | not null
 creation_time | timestamp with time zone | not null default now()
 edition_time  | timestamp with time zone | not null default now()
 project_id    | bigint                   | not null
 location      | double3d                 | not null
 reviewer_id   | integer                  | not null default (-1)
 review_time   | timestamp with time zone |
 editor_id     | integer                  |
 parent_id     | bigint                   |
 radius        | double precision         | not null default 0
 confidence    | integer                  | not null default 5
 skeleton_id   | bigint                   |
Indexes:
    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE CONSTRAINT, btree (id)
    "skeleton_id_treenode_index" btree (skeleton_id)
    "treenode_editor_index" btree (editor_id)
    "treenode_location_x_index" btree (((location).x))
    "treenode_location_y_index" btree (((location).y))
    "treenode_location_z_index" btree (((location).z))
    "treenode_parent_id" btree (parent_id)
    "treenode_user_index" btree (user_id)

संपादित करें: यह परिणाम है, जब मैं @ypercube द्वारा प्रस्तावित क्वेरी (और इंडेक्स) का उपयोग करता हूं (क्वेरी लगभग 5.3 सेकंड के बिना EXPLAIN ANALYZE):

EXPLAIN ANALYZE SELECT u.id, ( SELECT COUNT(*) FROM treenode AS t WHERE t.project_id=1 AND t.user_id = u.id ) AS number_of_nodes FROM auth_user As u;
                                                                        QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on auth_user u  (cost=0.00..6987937.85 rows=46 width=4) (actual time=29.934..5556.147 rows=46 loops=1)
   SubPlan 1
     ->  Aggregate  (cost=151911.65..151911.66 rows=1 width=0) (actual time=120.780..120.780 rows=1 loops=46)
           ->  Bitmap Heap Scan on treenode t  (cost=4634.41..151460.44 rows=180486 width=0) (actual time=13.785..114.021 rows=145174 loops=46)
                 Recheck Cond: ((project_id = 1) AND (user_id = u.id))
                 Rows Removed by Index Recheck: 461076
                 ->  Bitmap Index Scan on treenode_user_index  (cost=0.00..4589.29 rows=180486 width=0) (actual time=13.082..13.082 rows=145174 loops=46)
                       Index Cond: ((project_id = 1) AND (user_id = u.id))
 Total runtime: 5556.190 ms
(9 rows)

Time: 5556.804 ms

संपादित करें 2: यह, परिणाम है जब मैं एक का उपयोग indexपर project_id, user_id(लेकिन कोई स्कीमा अनुकूलन, अभी तक) @ इरविन-brandstetter के रूप में (अपने मूल प्रश्न के रूप में एक ही गति से 1.5 सेकंड के साथ क्वेरी रन) का सुझाव दिया:

EXPLAIN ANALYZE SELECT user_id, count(user_id) as ct FROM treenode WHERE project_id=1 GROUP BY user_id;
                                                        QUERY PLAN                                                      
---------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=253670.88..253671.24 rows=37 width=8) (actual time=1807.334..1807.339 rows=38 loops=1)
   ->  Seq Scan on treenode  (cost=0.00..220280.62 rows=6678050 width=8) (actual time=0.183..893.491 rows=6678050 loops=1)
         Filter: (project_id = 1)
 Total runtime: 1807.368 ms
(4 rows)

आप भी एक मेज है Usersसाथ user_idप्राथमिक कुंजी के रूप?
ypercube y

मैंने अभी देखा कि पोस्टग्रेज के लिए थर्ड पार्टी कॉलमस्टोर एडऑन है। इसके अलावा, मैं सिर्फ नए ios ऐप से पोस्ट करना चाहता था
swasheck

2
अच्छे, स्पष्ट, पूर्ण प्रश्न के लिए धन्यवाद - संस्करण, तालिका परिभाषाएं, आदि
क्रेग रिंगर

@ypercube हाँ, मुझे एक उपयोगकर्ता तालिका मिली है।
15

कितने अलग project_idऔर user_id? क्या तालिका निरंतर रूप से अपडेट की जाती है या आप भौतिक दृष्टि से (कुछ समय के लिए) काम कर सकते हैं?
इरविन ब्रान्डेसटेटर

जवाबों:


25

मुख्य समस्या लापता सूचकांक है। लेकिन और भी है।

SELECT user_id, count(*) AS ct
FROM   treenode
WHERE  project_id = 1
GROUP  BY user_id;
  • आपके पास कई bigintकॉलम हैं। शायद overkill। आमतौर पर, integerजैसे कॉलम project_idऔर के लिए पर्याप्त से अधिक है user_id। यह भी अगले मद में मदद मिलेगी।
    तालिका परिभाषा का अनुकूलन करते समय, डेटा संरेखण और पैडिंग पर जोर देने के साथ, इस संबंधित उत्तर पर विचार करें । लेकिन बाकी के अधिकांश लागू होता है:

  • कमरे में हाथी : वहाँ कोई है पर सूचकांकproject_id । एक बनाए। यह इस उत्तर के बाकी हिस्सों की तुलना में अधिक महत्वपूर्ण है।
    इस पर बने रहते हुए, इसे एक बहुरंगी सूचकांक बनाएं:

    CREATE INDEX treenode_project_id_user_id_index ON treenode (project_id, user_id);

    यदि आपने मेरी सलाह का पालन किया, integerतो यहां सही होगा:

  • user_idपरिभाषित किया गया है NOT NULL, इसलिए count(user_id)इसके बराबर है count(*), लेकिन बाद वाला थोड़ा छोटा और तेज है। (इस विशिष्ट क्वेरी में, यह user_idपरिभाषित किए बिना भी लागू होगा NOT NULL।)

  • idपहले से ही प्राथमिक कुंजी है, अतिरिक्त UNIQUEबाधा बेकार गिट्टी है । जाने दो:

    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE CONSTRAINT, btree (id)

    एक तरफ: मैं idकॉलम नाम के रूप में उपयोग नहीं करूंगा । कुछ वर्णनात्मक का उपयोग करें जैसे treenode_id

जानकारी जोड़ी गई

क्यू: How many different project_id and user_id?
not more than five different project_id:।

इसका मतलब है कि Postgres को आपकी क्वेरी को पूरा करने के लिए पूरी तालिका का लगभग 20% पढ़ना होगा । जब तक यह केवल इंडेक्स-स्कैन का उपयोग नहीं कर सकता, तब तक किसी भी इंडेक्स को शामिल करने की तुलना में टेबल पर एक अनुक्रमिक स्कैन तेजी से होगा। तालिका और सर्वर सेटिंग्स को अनुकूलित करने के अलावा कोई और प्रदर्शन यहाँ नहीं है।

के रूप में सूचकांक-केवल स्कैन : यह देखने के कैसे प्रभावी हो सकता है कि, रन VACUUM ANALYZEआपको लगता है कि (ताले विशेष रूप से तालिका) खर्च कर सकते हैं यदि। फिर अपनी क्वेरी फिर से आज़माएं। यह अब केवल सूचकांक का उपयोग करके मध्यम रूप से तेज होना चाहिए । इस संबंधित उत्तर को पहले पढ़ें:

साथ ही मैनुअल पेज Postgres 9.6 और Postgres Wiki के साथ इंडेक्स-ओनली स्कैन पर जोड़ा गया


1
इरविन, आपके सुझावों के लिए धन्यवाद। आप सही हैं, के लिए user_idऔर project_id integerपर्याप्त से अधिक होना चाहिए। का उपयोग count(*)करने के बजाय count(user_id)70ms के बारे में यहाँ की बचत होती है, कि के अच्छे पता करने के लिए। मैंने EXPLAIN ANALYZEआपके सुझाव indexको पहली पोस्ट में जोड़ने के बाद क्वेरी को जोड़ा है । यह प्रदर्शन में सुधार नहीं करता है, हालांकि (लेकिन यह भी चोट नहीं करता है)। ऐसा लगता है indexकि इसका इस्तेमाल बिल्कुल नहीं किया गया है। मैं जल्द ही स्कीमा अनुकूलन का परीक्षण करूंगा।
टॉम्का

1
यदि मैं अक्षम करता हूं seqscan, तो इंडेक्स का उपयोग किया जाता है ( Index Only Scan using treenode_project_id_user_id_index on treenode), लेकिन क्वेरी लगभग 2.5 सेकंड लेती है (जो seqfcan के साथ लगभग 1 सेकंड लंबा है)।
टॉम्का

1
आपके अद्यतन के लिए धन्यवाद। ये गायब बिट्स मेरे सवाल का हिस्सा होना चाहिए था, यह सही है। मैं सिर्फ उनके प्रभाव के बारे में पता नहीं था। मैं अपने स्कीमा का अनुकूलन करूँगा जैसे आपने सुझाव दिया था --- आइए देखें कि मैं इससे क्या हासिल कर सकता हूं। आपके स्पष्टीकरण के लिए धन्यवाद, यह मेरे लिए समझ में आता है और इसलिए मैं आपके उत्तर को स्वीकृत के रूप में चिह्नित करूंगा।
टॉमका

7

मैं पहली बार एक सूचकांक जोड़ूंगा (project_id, user_id) और फिर 9.3 संस्करण में जोड़ूंगा, इस प्रश्न को आज़माएँ:

SELECT u.user_id, c.number_of_nodes 
FROM users AS u
   , LATERAL
     ( SELECT COUNT(*) AS number_of_nodes 
       FROM treenode AS t
       WHERE t.project_id = 1 
         AND t.user_id = u.user_id
     ) c 
-- WHERE c.number_of_nodes > 0 ;   -- you probably want this as well
                                   -- to show only relevant users

9.2 में, इसे आज़माएँ:

SELECT u.user_id, 
       ( SELECT COUNT(*) 
         FROM treenode AS t
         WHERE t.project_id = 1 
           AND t.user_id = u.user_id
       ) AS number_of_nodes  
FROM users AS u ;

मुझे लगता है कि आपके पास एक usersमेज है। यदि नहीं, तो इसके usersसाथ बदलें :
(SELECT DISTINCT user_id FROM treenode)


उत्तर देने के लिए आपका धन्यवाद। आप सही हैं, मुझे एक उपयोगकर्ता तालिका मिली है। हालांकि, 9.2 में अपनी क्वेरी का उपयोग करने पर परिणाम प्राप्त करने में लगभग 5 सेकंड लगते हैं - भले ही सूचकांक बनाया गया हो या नहीं। मैंने इस तरह सूचकांक बनाया: CREATE INDEX treenode_user_index ON treenode USING btree (project_id, user_id);लेकिन मैंने भी बिना USINGखंड के प्रयास किया। क्या मुझे कुछ याद है?
15

usersतालिका में कितनी पंक्ति हैं और क्वेरी में कितनी पंक्तियाँ हैं (तो कितने उपयोगकर्ता हैं project_id=1)? क्या आपने इंडेक्स जोड़ने के बाद इस क्वेरी की व्याख्या दिखा सकते हैं?
ypercube y 15

1
पहले, मैं अपनी पहली टिप्पणी में गलत था। आपके सुझाए गए सूचकांक के बिना परिणाम प्राप्त करने में लगभग 40s (!) लगते हैं। यह जगह में लगभग 5s लेता indexहै। गलतफहमी के लिए खेद है। मेरी usersतालिका में मुझे 46 प्रविष्टियाँ मिली हैं। क्वेरी केवल 9 पंक्तियाँ देता है। हैरानी की बात है, SELECT DISTINCT user_id FROM treenode WHERE project_id=1;38 पंक्तियों को लौटाता है। मैंने explainअपनी पहली पोस्ट में जोड़ा है । और भ्रम को रोकने के लिए: मेरी usersतालिका वास्तव में कहा जाता है auth_user
टॉमका

मुझे आश्चर्य है कि कैसे SELECT DISTINCT user_id FROM treenode WHERE project_id=1;38 पंक्तियों को वापस किया जा सकता है जबकि प्रश्न केवल 9 को लौटाते हैं।
ypercube y

क्या आप यह कोशिश कर सकते हैं ?:SET enable_seqscan = OFF; (Query); SET enable_seqscan = ON;
ypercube try
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.