मैं इस MySQL क्वेरी को आगे कैसे अनुकूलित कर सकता हूं?


9

मेरे पास एक क्वेरी है जिसे चलाने के लिए एक विशेष रूप से लंबा समय लग रहा है (15+ सेकंड) और यह केवल समय के साथ खराब हो रहा है क्योंकि मेरा डेटासेट बढ़ता है। मैंने इसे अतीत में अनुकूलित किया है, और सूचकांकों, कोड-स्तरीय छंटाई और अन्य अनुकूलन को जोड़ा है, लेकिन इसे कुछ और परिष्कृत करने की आवश्यकता है।

SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM `sounds` 
INNER JOIN ratings ON sounds.id = ratings.rateable_id 
WHERE (ratings.rateable_type = 'Sound' 
   AND sounds.blacklisted = false 
   AND sounds.ready_for_deployment = true 
   AND sounds.deployed = true 
   AND sounds.type = "Sound" 
   AND sounds.created_at > "2011-03-26 21:25:49") 
GROUP BY ratings.rateable_id

क्वेरी का उद्देश्य मुझे sound idसबसे हाल ही में जारी की गई ध्वनियों की औसत रेटिंग प्राप्त करना है। लगभग 1500 ध्वनियाँ हैं, और 2 मिलियन रेटिंग हैं।

मेरे पास कई सूचकांक हैं sounds

mysql> show index from sounds;
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+————+
| Table  | Non_unique | Key_name                                 | Seq_in_index | Column_name          | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+————+
| sounds |          0 | PRIMARY                                  |            1 | id                   | A         |        1388 |     NULL | NULL   |      | BTREE      |         | 
| sounds |          1 | sounds_ready_for_deployment_and_deployed |            1 | deployed             | A         |           5 |     NULL | NULL   | YES  | BTREE      |         | 
| sounds |          1 | sounds_ready_for_deployment_and_deployed |            2 | ready_for_deployment | A         |          12 |     NULL | NULL   | YES  | BTREE      |         | 
| sounds |          1 | sounds_name                              |            1 | name                 | A         |        1388 |     NULL | NULL   |      | BTREE      |         | 
| sounds |          1 | sounds_description                       |            1 | description          | A         |        1388 |      128 | NULL   | YES  | BTREE      |         | 
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+

और कई पर ratings

mysql> show index from ratings;
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+————+
| Table   | Non_unique | Key_name                                | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+————+
| ratings |          0 | PRIMARY                                 |            1 | id          | A         |     2008251 |     NULL | NULL   |      | BTREE      |         | 
| ratings |          1 | index_ratings_on_rateable_id_and_rating |            1 | rateable_id | A         |          18 |     NULL | NULL   |      | BTREE      |         | 
| ratings |          1 | index_ratings_on_rateable_id_and_rating |            2 | rating      | A         |        9297 |     NULL | NULL   | YES  | BTREE      |         | 
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

यहाँ है EXPLAIN

mysql> EXPLAIN SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM sounds INNER JOIN ratings ON sounds.id = ratings.rateable_id WHERE (ratings.rateable_type = 'Sound' AND sounds.blacklisted = false AND sounds.ready_for_deployment = true AND sounds.deployed = true AND sounds.type = "Sound" AND sounds.created_at > "2011-03-26 21:25:49") GROUP BY ratings.rateable_id;
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+——————+
| id | select_type | table   | type   | possible_keys                                    | key                                     | key_len | ref                                     | rows    | Extra       |
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+——————+
|  1 | SIMPLE      | ratings | index  | index_ratings_on_rateable_id_and_rating          | index_ratings_on_rateable_id_and_rating | 9       | NULL                                    | 2008306 | Using where | 
|  1 | SIMPLE      | sounds  | eq_ref | PRIMARY,sounds_ready_for_deployment_and_deployed | PRIMARY                                 | 4       | redacted_production.ratings.rateable_id |       1 | Using where | 
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+-------------+

मैं प्राप्त परिणामों को एक बार कैश कर देता हूं, इसलिए साइट प्रदर्शन बहुत अधिक समस्या नहीं है, लेकिन इस कॉल के इतने लंबे समय तक चलने के कारण मेरे कैश वार्मर्स को लंबा और लंबा चलना है, और यह एक मुद्दा बनने लगा है। यह एक क्वेरी में क्रंच करने के लिए बहुत सी संख्याओं की तरह नहीं लगता ...

इस प्रदर्शन को बेहतर बनाने के लिए मैं और क्या कर सकता हूं ?


क्या आप EXPLAINआउटपुट दिखा सकते हैं ? EXPLAIN SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM sounds INNER JOIN ratings ON sounds.id = ratings.rateable_id WHERE (ratings.rateable_type = 'Sound' AND sounds.blacklisted = false AND sounds.ready_for_deployment = true AND sounds.deployed = true AND sounds.type = "Sound" AND sounds.created_at > "2011-03-26 21:25:49") GROUP BY ratings.rateable_id
डेरेक डाउनी

@coneybeare आज मेरे लिए बहुत ही रोचक चुनौती थी !!! आप सवाल के लिए +1। मैं चाहता हूं कि निकट भविष्य में इस तरह के और प्रश्न आए।
रोलैंडमाइसीडीडीबीए

@coneybeare ऐसा लगता है कि नई EXPLAIN केवल 2,008,306 के बजाय 21540 पंक्तियों (359 X 60) को पढ़ती है। कृपया मेरे उत्तर में मेरे द्वारा सुझाई गई क्वेरी पर चर्चा करें। मैं उस से आने वाली पंक्तियों की संख्या देखना चाहूंगा।
रोलैंडमाइसीडीडीबीए

@RolandoMySQLDBA नई व्याख्या वास्तव में दिखाती है कि सूचकांक के साथ पंक्तियों की छोटी मात्रा, हालांकि, क्वेरी को निष्पादित करने का समय अभी भी लगभग 15 सेकंड था, कोई सुधार नहीं दिखा रहा है
coneybeare

@coneybeare मैंने क्वेरी को ठीक किया। कृपया मेरी नई क्वेरी पर EXPLAIN चलाएँ। मैंने इसे अपने जवाब में जोड़ दिया।
रोलैंडमाइसीडीडीबीए

जवाबों:


7

क्वेरी, तालिकाओं और WHERE AND GROUP को क्लॉस पर देखने के बाद, मैं निम्नलिखित की सिफारिश करता हूं:

सिफारिश # 1) क्वेरी को रिफलेक्टर करें

मैंने तीन (3) चीजें करने के लिए क्वेरी को पुनर्गठित किया:

  1. छोटे अस्थायी टेबल बनाएं
  2. उन टेम्प टेबलों पर WHERE क्लॉज की प्रक्रिया करें
  3. देरी बहुत अंतिम में शामिल हो रही है

यहाँ मेरी प्रस्तावित क्वेरी है:

SELECT
  sounds.*,srkeys.avg_rating,srkeys.votes
FROM
(
  SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes
  (
    SELECT id FROM sounds
    WHERE blacklisted = false 
    AND   ready_for_deployment = true 
    AND   deployed = true 
    AND   type = "Sound" 
    AND   created_at > '2011-03-26 21:25:49'
  ) AA INNER JOIN
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
  ) BB
  ON AA.id = BB.rateable_id
  GROUP BY BB.rateable_id
) srkeys INNER JOIN sounds USING (id);

अनुशंसा # 2) ध्वनि तालिका को अनुक्रमणिका के साथ अनुक्रमित करता है जो WHERE खंड को समायोजित करेगा

इस इंडेक्स के कॉलम में WHERE क्लॉज से सभी कॉलम शामिल हैं जिसमें पहले स्टैटिक वैल्यू और लास्ट टार्गेट लास्ट है

ALTER TABLE sounds ADD INDEX support_index
(blacklisted,ready_for_deployment,deployed,type,created_at);

मुझे पूरा विश्वास है कि आप सुखद आश्चर्यचकित होंगे। कोशिश करो !!!

अद्यतन 2011-05-21 19:04

मैंने सिर्फ कार्डिनलिटी देखी। OUCH !!! Rateable_id के लिए 1 की कार्डिनैलिटी। लड़का, मुझे बेवकूफ लगता है !!!

UPDATE 2011-05-21 19:20

हो सकता है कि चीजों को बेहतर बनाने के लिए सूचकांक बनाना काफी होगा।

UPDATE 2011-05-21 22:56

कृपया इसे चलाएं:

EXPLAIN SELECT
  sounds.*,srkeys.avg_rating,srkeys.votes
FROM
(
  SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes FROM
  (
    SELECT id FROM sounds
    WHERE blacklisted = false 
    AND   ready_for_deployment = true 
    AND   deployed = true 
    AND   type = "Sound" 
    AND   created_at > '2011-03-26 21:25:49'
  ) AA INNER JOIN
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
  ) BB
  ON AA.id = BB.rateable_id
  GROUP BY BB.rateable_id
) srkeys INNER JOIN sounds USING (id);

अद्यतन 2011-05-21 23:34

मैंने इसे फिर से लागू किया। यह एक कोशिश करें:

EXPLAIN
  SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes FROM
  (
    SELECT id FROM sounds
    WHERE blacklisted = false 
    AND   ready_for_deployment = true 
    AND   deployed = true 
    AND   type = "Sound" 
    AND   created_at > '2011-03-26 21:25:49'
  ) AA INNER JOIN
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
  ) BB
  ON AA.id = BB.rateable_id
  GROUP BY BB.rateable_id
;

UPDATE 2011-05-21 23:55

मैंने इसे फिर से लागू किया। कृपया इसे आजमाएं (अंतिम बार):

EXPLAIN
  SELECT A.id,avg(B.rating) AS avg_rating, count(B.rating) AS votes FROM
  (
    SELECT BB.* FROM
    (
      SELECT id FROM sounds
      WHERE blacklisted = false 
      AND   ready_for_deployment = true 
      AND   deployed = true 
      AND   type = "Sound" 
      AND   created_at > '2011-03-26 21:25:49'
    ) AA INNER JOIN sounds BB USING (id)
  ) A INNER JOIN
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
  ) B
  ON A.id = B.rateable_id
  GROUP BY B.rateable_id;

UPDATE 2011-05-22 00:12

मुझे देने से नफरत है !!!!

EXPLAIN
  SELECT A.*,avg(B.rating) AS avg_rating, count(B.rating) AS votes FROM
  (
    SELECT BB.* FROM
    (
      SELECT id FROM sounds
      WHERE blacklisted = false 
      AND   ready_for_deployment = true 
      AND   deployed = true 
      AND   type = "Sound" 
      AND   created_at > '2011-03-26 21:25:49'
    ) AA INNER JOIN sounds BB USING (id)
  ) A,
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
    AND AAA.rateable_id = A.id
  ) B
  GROUP BY B.rateable_id;

UPDATE 2011-05-22 07:51

यह मुझे परेशान कर रहा है कि रेटिंग एक्सप्लेन में 2 मिलियन पंक्तियों के साथ वापस आ रही है। फिर, इसने मुझे मारा। आपको रेटिंग टेबल पर एक और सूचकांक की आवश्यकता हो सकती है, जो rateable_type से शुरू होता है:

ALTER TABLE ratings ADD INDEX
rateable_type_rateable_id_ndx (rateable_type,rateable_id);

इस सूचकांक का लक्ष्य अस्थायी तालिका को कम करना है जो रेटिंग में हेरफेर करता है ताकि यह कम से कम 2 मिलियन हो। यदि हम उस अस्थायी तालिका को काफी कम (कम से कम आधा) प्राप्त कर सकते हैं, तो हम आपकी क्वेरी और मेरा तेजी से काम करने की बेहतर उम्मीद कर सकते हैं।

उस इंडेक्स को बनाने के बाद, कृपया मेरी मूल प्रस्तावित क्वेरी को पुनः प्रयास करें और अपना प्रयास करें:

SELECT
  sounds.*,srkeys.avg_rating,srkeys.votes
FROM
(
  SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes
  (
    SELECT id FROM sounds
    WHERE blacklisted = false 
    AND   ready_for_deployment = true 
    AND   deployed = true 
    AND   type = "Sound" 
    AND   created_at > '2011-03-26 21:25:49'
  ) AA INNER JOIN
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
  ) BB
  ON AA.id = BB.rateable_id
  GROUP BY BB.rateable_id
) srkeys INNER JOIN sounds USING (id);

अद्यतन 2011-05-22 18:39: अंतिम कार्य

मैंने एक संग्रहीत कार्यविधि में एक क्वेरी को रिफ्लेक्ट किया और चीजों को गति देने पर एक प्रश्न का उत्तर देने में मदद करने के लिए एक इंडेक्स जोड़ा। मुझे 6 उत्थान मिले, उत्तर स्वीकार कर लिया गया और 200 का इनाम मिला।

मैंने एक और क्वेरी (सीमांत परिणाम) भी रिफैक्ट किया था और एक इंडेक्स (नाटकीय परिणाम) जोड़ा था। मुझे 2 अपवोट मिले और जवाब स्वीकार था।

मैंने अभी तक एक अन्य क्वेरी चालान के लिए एक इंडेक्स जोड़ा और एक बार इसे अपग्रेड किया गया था

और अब आपका प्रश्न

इन जैसे (आपके सहित) सभी सवालों के जवाब देना चाहते थे, मैं एक YouTube वीडियो से प्रेरित था जो मैंने रिफलेक्टरिंग प्रश्नों पर देखा था।

फिर से शुक्रिया, @coneybeare !!! मैं इस सवाल का पूरी तरह से संभव जवाब देना चाहता था, न कि केवल अंक या प्रशंसा स्वीकार करना। अब, मैं महसूस कर सकता हूं कि मैंने अंक अर्जित किए !!!


मैंने सूचकांक जोड़ा, समय पर कोई सुधार नहीं हुआ। यहाँ नया EXPLAIN है: cloud.coneybeare.net/6y7c
coneybeare

अनुशंसा 1 से क्वेरी पर EXPLAIN : cloud.coneybeare.net/6xZ2 इस क्वेरी को चलाने में लगभग 30 सेकंड लगे
coneybeare

मुझे किसी कारण से आपके सिंटैक्स को थोड़ा संपादित करना पड़ा (मैंने पहली क्वेरी से एक FROM जोड़ा, और मुझे एएए उर्फ ​​से छुटकारा पाना था)। यहाँ EXPLAIN है: cloud.coneybeare.net/6xlq वास्तविक क्वेरी को चलाने में लगभग 30 सेकंड का समय लगता है
coneybeare

@RolandoMySQLDBA: अपने 23:55 अपडेट पर जारी रखें: cloud.coneybeare.net/6wrN वास्तविक क्वेरी एक मिनट से अधिक चली, इसलिए मैंने इस प्रक्रिया को मार डाला
coneybeare

दूसरा आंतरिक चयन ए चयन तालिका तक नहीं पहुंच सकता है, इस प्रकार एआईडी त्रुटि फेंकता है।
कॉनबीयर

3

EXPLAIN आउटपुट के लिए धन्यवाद। जैसा कि आप उस कथन से बता सकते हैं, इसका कारण यह है कि रेटिंग तालिका में पूर्ण तालमेल है। WHERE कथन में कुछ भी 2million पंक्तियों को फ़िल्टर नहीं कर रहा है।

आप Rating.type पर एक इंडेक्स जोड़ सकते हैं, लेकिन मेरा अनुमान है कि CARDINALITY वास्तविक कम होने वाली है और आप अभी भी कुछ पंक्तियों को स्कैन कर रहे हैं ratings

वैकल्पिक रूप से आप mysql को ध्वनियों के सूचकांक का उपयोग करने के लिए बाध्य करने के लिए सूचकांक संकेतों का उपयोग करने का प्रयास कर सकते हैं।

अपडेट किया गया:

अगर यह मैं होता, तो मैं sounds.createdउस पर एक इंडेक्स जोड़ देता, जिसमें पंक्तियों को फ़िल्टर करने का सबसे अच्छा मौका होता है और संभवत: mysql क्वेरी ऑप्टिमाइज़र को लगता है कि टेबल इंडेक्स का उपयोग करने के लिए मजबूर करेगा। बस उन प्रश्नों से सावधान रहें जो लंबे समय से निर्मित समय-सीमा (1 वर्ष, 3 महीने) का उपयोग करते हैं, बस ध्वनियों के आकार के आकार पर निर्भर करता है)।


ऐसा लगता है कि आपका सुझाव @coneybeare के लिए उल्लेखनीय था। मेरे से भी +1।
रोलैंडमाइसीडीडीबीए

बनाया गया सूचकांक किसी भी समय बंद नहीं किया गया। यहाँ अद्यतन EXPLAIN है। cloud.coneybeare.net/6xvc
coneybeare

2

यदि इसे एक "ऑन-द-फ्लाई" उपलब्ध क्वेरी होना है, तो यह आपके विकल्पों को थोड़ा सीमित करता है।

मैं इस समस्या के लिए विभाजित और जीत का सुझाव देने जा रहा हूं।

--
-- Create an in-memory table
CREATE TEMPORARY TABLE rating_aggregates (
rateable_id INT,
avg_rating NUMERIC,
votes NUMERIC
);
--
-- For now, just aggregate. 
INSERT INTO rating_aggregates
SELECT ratings.rateable_id, 
avg(ratings.rating) AS avg_rating, 
count(ratings.rating) AS votes FROM `sounds`  
WHERE ratings.rateable_type = 'Sound' 
GROUP BY ratings.rateable_id;
--
-- Now get your final product --
SELECT 
sounds.*, 
rating_aggregates.avg_rating, 
rating_aggregates.votes AS votes,
rating_aggregates.rateable_id 
FROM rating_aggregates 
INNER JOIN sounds ON (sounds.id = rating_aggregates.rateable_id) 
WHERE 
ratings.rateable_type = 'Sound' 
   AND sounds.blacklisted = false 
   AND sounds.ready_for_deployment = true 
   AND sounds.deployed = true 
   AND sounds.type = "Sound" 
   AND sounds.created_at > "2011-03-26 21:25:49";

लगता है @coneybeare ने आपके सुझाव में कुछ देखा। मुझ से +1 !!!
रोलैंडमाइसीडीडीबीए

मैं वास्तव में यह काम नहीं कर सका। मुझे sql त्रुटियां हो रही थीं कि मैं इस बारे में अनिश्चित था कि कैसे संपर्क किया जाए। मैंने वास्तव में अस्थायी तालिकाओं के साथ कभी काम नहीं किया है
कॉनबीयर

मैं (मैं से जोड़ने के लिए किया था अंत में इसे पाने के लिए किया था sounds, ratingsबीच क्वेरी के लिए), लेकिन यह मेरी एसक्यूएल बॉक्स बंद कर दिया और मैं इस प्रक्रिया को मारने के लिए किया था।
कंबाइबर

0

जोइन का प्रयोग करें, उपश्रेणियों का नहीं। क्या आपके किसी भी उपकेंद्र ने मदद की कोशिश की?

शो टेबल बना लगता है \ G

शो का निर्माण करें

अक्सर "यौगिक" अनुक्रमित होना फायदेमंद होता है, एकल-स्तंभ वाले नहीं। शायद INDEX (टाइप, create_at)

आप एक JOIN में दोनों तालिकाओं पर फ़िल्टर कर रहे हैं; यह एक प्रदर्शन समस्या होने की संभावना है।

लगभग 1500 ध्वनियां हैं, और 2 मिलियन रेटिंग हैं।

अनुशंसा करें कि आपके पास एक auto_increment id है ratings, एक सारांश तालिका बनाएं , और जहाँ आपने "छोड़ दिया है" का ट्रैक रखने के लिए AI आईडी का उपयोग करें। हालांकि, एक सारांश तालिका में औसत स्टोर न करें:

avg (Ratings.rating) के रूप में avg_rating,

इसके बजाय, एसयूएम (रेटिंग रेटिंग) रखें। औसत की गणना के लिए औसत का औसत गणितीय रूप से गलत है; (रकम का योग) / (मायने रखता है की राशि) सही है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.