ฉันจะเพิ่มประสิทธิภาพการสืบค้น 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คะแนนเฉลี่ยและจากเสียงล่าสุดที่ออกมา มีประมาณ 1,500 เสียงและ 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
Derek Downey

@coneybeare นี่เป็นความท้าทายที่น่าสนใจมากสำหรับฉันในวันนี้ !!! +1 สำหรับคำถามของคุณ ฉันต้องการคำถามเพิ่มเติมเช่นนี้มาในอนาคตอันใกล้
RolandoMySQLDBA

@coneybeare ดูเหมือนว่า EXPLAIN ใหม่จะอ่านเพียง 21540 แถว (359 X 60) แทน 2,008,306 กรุณาเรียกใช้อธิบายในแบบสอบถามที่ฉันแนะนำในคำตอบของฉัน ฉันต้องการดูจำนวนแถวที่มาจากที่นั้น
RolandoMySQLDBA

@RolandoMySQLDBA คำอธิบายใหม่นี้แสดงให้เห็นว่าจำนวนแถวที่น้อยลงพร้อมกับดัชนีอย่างไรก็ตามเวลาในการดำเนินการค้นหายังคงอยู่ประมาณ 15 วินาทีโดยไม่แสดงการปรับปรุง
coneybeare

@coneybeare ฉันปรับการค้นหา กรุณาเรียกใช้อธิบายในแบบสอบถามใหม่ของฉัน ฉันต่อท้ายมันเพื่อตอบ
RolandoMySQLDBA

คำตอบ:


7

หลังจากดูแบบสอบถามตารางและคำสั่ง WHERE AND GROUP BY ฉันขอแนะนำสิ่งต่อไปนี้:

คำแนะนำ # 1) สร้างการสอบถามใหม่

ฉันจัดระเบียบแบบสอบถามเพื่อทำสามสิ่ง (3):

  1. สร้างตาราง temp ที่เล็กลง
  2. ประมวลผลคำสั่งย่อย WHERE บนตาราง temp เหล่านั้น
  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 !!! Cardinality 1 สำหรับ rateable_id เด็กชายฉันรู้สึกโง่ !!!

ปรับปรุง 2011-05-21 19:20

อาจทำให้ดัชนีนั้นเพียงพอที่จะปรับปรุงสิ่งต่าง ๆ

ปรับปรุง 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
;

ปรับปรุง 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;

อัพเดท 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;

อัพเดท 2011-05-22 07:51

มันทำให้ฉันรำคาญใจที่เรตติ้งกลับมาอีก 2 ล้านแถวใน EXPLAIN จากนั้นมันก็ตีฉัน คุณอาจต้องการดัชนีอื่นในตารางการให้คะแนนซึ่งเริ่มต้นด้วย 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 upvotes และได้รับคำตอบแล้ว

ฉันได้เพิ่มดัชนีสำหรับการสืบค้นคำถามอื่นและได้รับการ upvoted หนึ่งครั้ง

และตอนนี้คำถามของคุณ

ต้องการที่จะตอบคำถามทุกข้อเช่นนี้ (รวมถึงของคุณ) ได้รับแรงบันดาลใจจากวิดีโอ YouTube ที่ฉันดูในการปรับโครงสร้างแบบสอบถาม

ขอขอบคุณอีกครั้ง @coneybeare !!! ฉันต้องการตอบคำถามนี้อย่างเต็มที่เท่าที่จะทำได้ไม่ใช่แค่ยอมรับคะแนนหรือรางวัล ตอนนี้ฉันรู้สึกว่าฉันได้รับคะแนน !!!


ฉันเพิ่มดัชนีไม่มีการปรับปรุงตรงเวลา นี่คือคำอธิบายใหม่: cloud.coneybeare.net/6y7c
coneybeare

อธิบายเกี่ยวกับแบบสอบถามจากคำแนะนำที่ 1: cloud.coneybeare.net/6xZ2ใช้เวลาประมาณ 30 วินาทีในการเรียกใช้
คิวรี

ฉันต้องแก้ไขไวยากรณ์ของคุณเล็กน้อยด้วยเหตุผลบางอย่าง (ฉันเพิ่ม FROM ก่อนการสืบค้นแรกและฉันต้องกำจัดนามแฝง AAA) นี่คือคำอธิบาย: cloud.coneybeare.net/6xlq การค้นหาจริงใช้เวลาประมาณ 30 วินาทีในการทำงาน
coneybeare

@RolandoMySQLDBA: อธิบายเกี่ยวกับการอัปเดต 23:55 ของคุณ: cloud.coneybeare.net/6wrNการค้นหาที่เกิดขึ้นจริงใช้เวลานานกว่าหนึ่งนาทีดังนั้นฉันจึงฆ่ากระบวนการ
coneybeare

ตัวเลือกด้านในที่สองไม่สามารถเข้าถึงตาราง A ได้ดังนั้น A.id จะเกิดข้อผิดพลาด
coneybeare

3

ขอบคุณสำหรับการส่งออกอธิบาย ดังที่คุณสามารถบอกได้จากคำแถลงดังกล่าวเหตุผลที่ใช้เวลานานมากก็คือตารางเต็มรูปแบบบนตารางอันดับ ไม่มีสิ่งใดในคำสั่ง WHERE ที่กรองแถว 2 ล้านลง

คุณสามารถเพิ่มดัชนีใน ratings.type แต่ฉันเดา cardinality ratingsเป็นไปจะต่ำจริงและคุณจะยังคงได้รับการสแกนค่อนข้างไม่กี่แถวบน

หรือคุณสามารถลองใช้คำแนะนำดัชนีเพื่อบังคับให้ mysql ใช้ดัชนีเสียงได้

Updated:

ถ้าเป็นฉันฉันจะเพิ่มดัชนีsounds.createdเนื่องจากมีโอกาสที่ดีที่สุดในการกรองแถวและอาจบังคับให้เครื่องมือเพิ่มประสิทธิภาพแบบสอบถาม mysql ใช้ดัชนีตารางเสียง เพียงแค่ระวังการสืบค้นที่ใช้กรอบเวลาที่สร้างขึ้นมานาน (1 ปี 3 เดือนขึ้นอยู่กับขนาดของตารางเสียง)


ดูเหมือนว่าข้อเสนอแนะของคุณโดดเด่นสำหรับ @coneybeare +1 จากฉันเช่นกัน
RolandoMySQLDBA

ดัชนีที่สร้างไม่ได้โกนทิ้งเมื่อใดก็ได้ นี่คือ EXPLAIN ที่อัปเดต cloud.coneybeare.net/6xvc
coneybeare

2

หากสิ่งนี้จะต้องมีแบบสอบถามแบบ"on-the-fly"นั่นจะเป็นการ จำกัด ตัวเลือกของคุณเล็กน้อย

ฉันจะแนะนำให้แบ่งและพิชิตปัญหานี้

--
-- 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 จากฉัน !!!
RolandoMySQLDBA

จริง ๆ แล้วฉันไม่สามารถทำงานนี้ได้ ฉันได้รับข้อผิดพลาด sql ที่ฉันไม่แน่ใจว่าจะเข้าใกล้ได้อย่างไร ฉันไม่เคยทำงานกับตารางชั่วคราวจริงๆ
coneybeare

ฉันไม่ได้รับมันในที่สุด (ฉันมีการเพิ่มจากsounds, ratingsการแบบสอบถามกลาง) แต่มันล็อคขึ้นกล่อง SQL ของฉันและฉันต้องฆ่ากระบวนการ
coneybeare

0

ใช้เข้าร่วมไม่ใช่แบบสอบถามย่อย คำถามย่อยใด ๆ ที่คุณพยายามช่วย

แสดงเสียงของตารางสร้าง \ G

จัดอันดับแสดง SHOW CREATE TABLE \ G

บ่อยครั้งที่มีประโยชน์ที่จะมีดัชนี "ผสม" ไม่ใช่คอลัมน์เดี่ยว อาจจะเป็น INDEX (ประเภท, created_at)

คุณกำลังกรองทั้งสองตารางใน JOIN; ที่น่าจะเป็นปัญหาด้านประสิทธิภาพ

มีประมาณ 1,500 เสียงและ 2 ล้านการจัดอันดับ

แนะนำให้คุณเปิดใช้ id auto_increment ratingsสร้างตารางสรุปและใช้ AI id ในการติดตามตำแหน่งที่คุณ "ปิด" อย่างไรก็ตามอย่าเก็บค่าเฉลี่ยในตารางสรุป:

เฉลี่ย (ให้คะแนนการให้คะแนน) AS avg_rating,

ให้เก็บ SUM (ให้คะแนนการจัดอันดับ) แทน ค่าเฉลี่ยของค่าเฉลี่ยไม่ถูกต้องทางคณิตศาสตร์สำหรับการคำนวณค่าเฉลี่ย (ผลรวมของผลรวม) / (จำนวนผลรวม) ถูกต้อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.