Làm thế nào tôi có thể tối ưu hóa truy vấn MySQL này hơn nữa?


9

Tôi có một truy vấn mất nhiều thời gian để chạy (15+ giây) và nó chỉ trở nên tồi tệ hơn theo thời gian khi dữ liệu của tôi tăng lên. Tôi đã tối ưu hóa điều này trong quá khứ và đã thêm các chỉ mục, sắp xếp cấp mã và tối ưu hóa khác, nhưng nó cần một số tinh chỉnh thêm.

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

Mục đích của truy vấn là giúp tôi sound idđánh giá mức trung bình của các âm thanh được phát hành gần đây nhất. Có khoảng 1500 âm thanh và 2 triệu xếp hạng.

Tôi có một vài chỉ số trên 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      |         | 
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+

và một số trên 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      |         | 
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

Đây là 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 | 
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+-------------+

Tôi thực hiện lưu trữ kết quả sau khi nhận được, do đó hiệu suất trang web không phải là vấn đề, nhưng bộ đệm bộ đệm của tôi mất nhiều thời gian hơn và lâu hơn do cuộc gọi này mất quá nhiều thời gian và điều đó bắt đầu trở thành một vấn đề. Điều này dường như không có nhiều con số để khủng hoảng trong một truy vấn

Tôi có thể làm gì nhiều hơn để làm cho điều này thực hiện tốt hơn ?


Bạn có thể hiển thị EXPLAINđầu ra? 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 Đây là một thử thách rất thú vị đối với tôi hôm nay !!! +1 cho bạn câu hỏi. Tôi muốn nhiều câu hỏi như thế này sẽ xuất hiện trong tương lai gần.
RolandoMySQLDBA

@coneybeare Có vẻ như EXPLAIN mới chỉ đọc được 21540 hàng (359 X 60) thay vì 2.008.306. Vui lòng chạy GIẢI THÍCH cho truy vấn mà tôi đề xuất ban đầu trong câu trả lời của mình. Tôi muốn xem số lượng hàng xuất phát từ đó.
RolandoMySQLDBA

@RolandoMySQLDBA Giải thích mới thực sự cho thấy số lượng hàng nhỏ hơn với chỉ mục, tuy nhiên, thời gian để thực hiện truy vấn vẫn còn khoảng 15 giây, không cho thấy sự cải thiện
coneybeare

@coneybeare Tôi tinh chỉnh truy vấn. Vui lòng chạy EXPLAIN trên truy vấn mới của tôi. Tôi nối nó vào câu trả lời của tôi.
RolandoMySQLDBA

Câu trả lời:


7

Sau khi xem qua các truy vấn, các bảng và các mệnh đề WHERE AND GROUP BY, tôi khuyên bạn nên như sau:

Khuyến nghị số 1) Tái cấu trúc truy vấn

Tôi sắp xếp lại truy vấn để thực hiện ba (3) điều:

  1. tạo các bảng tạm thời nhỏ hơn
  2. Xử lý mệnh đề WHERE trên các bảng tạm thời
  3. Trì hoãn tham gia đến cuối cùng

Đây là truy vấn đề xuất của tôi:

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);

Khuyến nghị số 2) Lập chỉ mục bảng âm thanh với chỉ mục phù hợp với mệnh đề WHERE

Các cột của chỉ mục này bao gồm tất cả các cột từ mệnh đề WHERE với các giá trị tĩnh trước và mục tiêu di chuyển cuối cùng

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

Tôi chân thành tin rằng bạn sẽ ngạc nhiên thú vị. Hãy thử một lần !!!

CẬP NHẬT 2011-05-21 19:04

Tôi chỉ thấy cardinality. NGOÀI RA !!! Cardinality của 1 cho ratizable_id. Chàng trai, tôi cảm thấy thật ngu ngốc !!!

CẬP NHẬT 2011-05-21 19:20

Có lẽ làm cho chỉ số sẽ là đủ để cải thiện mọi thứ.

CẬP NHẬT 2011-05-21 22:56

Vui lòng chạy cái này:

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);

CẬP NHẬT 2011-05-21 23:34

Tôi tái cấu trúc lại nó. Hãy thử cái này

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
;

CẬP NHẬT 2011-05-21 23:55

Tôi tái cấu trúc lại nó. Hãy dùng thử cái này (Lần trước):

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;

CẬP NHẬT 2011-05-22 00:12

Tôi ghét bỏ cuộc !!!!

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;

CẬP NHẬT 2011-05-22 07:51

Điều đó đã làm phiền tôi rằng xếp hạng đang quay trở lại với 2 triệu hàng trong GIẢI THÍCH. Sau đó, nó đánh tôi. Bạn có thể cần một chỉ mục khác trên bảng xếp hạng bắt đầu bằng ratizable_type:

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

Mục tiêu của chỉ số này là giảm bảng tạm thời thao túng xếp hạng sao cho thấp hơn 2 triệu. Nếu chúng ta có thể có được bảng tạm thời nhỏ hơn đáng kể (ít nhất là một nửa), thì chúng ta có thể có hy vọng tốt hơn trong truy vấn của bạn và tôi cũng làm việc nhanh hơn.

Sau khi tạo chỉ mục đó, vui lòng Thử lại truy vấn được đề xuất ban đầu của tôi và cũng thử truy vấn của bạn:

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);

CẬP NHẬT 2011-05-22 18:39: CUỐI CÙNG

Tôi đã cấu trúc lại một truy vấn trong một thủ tục được lưu trữ và thêm một chỉ mục để giúp trả lời một câu hỏi về việc tăng tốc mọi thứ. Tôi đã nhận được 6 lượt upvote, đã có câu trả lời được chấp nhận và nhận được 200 tiền thưởng.

Tôi cũng đã tái cấu trúc một truy vấn khác (kết quả cận biên) và thêm một chỉ mục (kết quả ấn tượng). Tôi đã nhận được 2 upvote và đã chấp nhận câu trả lời.

Tôi đã thêm một chỉ mục cho một challange truy vấn khác và đã được nâng cấp một lần

và bây giờ câu hỏi của bạn .

Muốn trả lời tất cả các câu hỏi như thế này (bao gồm cả câu hỏi của bạn) được lấy cảm hứng từ một video YouTube tôi đã xem trên các truy vấn tái cấu trúc.

Cảm ơn bạn một lần nữa, @coneybeare !!! Tôi muốn trả lời câu hỏi này đến mức tối đa có thể, không chỉ chấp nhận điểm hoặc giải thưởng. Bây giờ, tôi có thể cảm thấy rằng tôi đã kiếm được điểm !!!


Tôi đã thêm chỉ số, không cải thiện về thời gian. Đây là GIẢI THÍCH mới: cloud.coneybeare.net/6y7c
coneybeare

GIẢI THÍCH về truy vấn từ đề xuất 1: cloud.coneybeare.net/6xZ2 Mất khoảng 30 giây để chạy truy vấn này
coneybeare

Tôi đã phải chỉnh sửa cú pháp của bạn một chút vì một số lý do (tôi đã thêm TỪ trước truy vấn đầu tiên và tôi phải loại bỏ bí danh AAA). Đây là GIẢI THÍCH: cloud.coneybeare.net/6xlq Truy vấn thực tế mất khoảng 30 giây để chạy
coneybeare

@RolandoMySQLDBA: GIẢI THÍCH về cập nhật 23:55 của bạn: cloud.coneybeare.net/6wrN Các ran truy vấn thực tế hơn một phút vì vậy tôi đã giết chết quá trình
coneybeare

Lựa chọn bên trong thứ hai không thể truy cập vào bảng chọn A, do đó A.id đưa ra lỗi.
coneybeare

3

Cảm ơn cho đầu ra GIẢI THÍCH. Như bạn có thể nói từ tuyên bố đó, lý do nó mất quá nhiều thời gian là toàn bộ bảng trên bảng xếp hạng. Không có gì trong câu lệnh WHERE đang lọc xuống các hàng 2 triệu.

Bạn có thể thêm một chỉ mục trên xếp hạng.type, nhưng tôi đoán là CARDINALITY sẽ ở mức rất thấp và bạn vẫn sẽ quét một vài hàng trên đó ratings.

Ngoài ra, bạn có thể thử sử dụng gợi ý chỉ mục để buộc mysql sử dụng các chỉ mục âm thanh.

Cập nhật:

Nếu là tôi, tôi sẽ thêm một chỉ mục vào sounds.createdđể có cơ hội lọc các hàng tốt nhất và có thể sẽ buộc trình tối ưu hóa truy vấn mysql sử dụng các chỉ mục bảng âm thanh. Chỉ cần cẩn thận với các truy vấn sử dụng các khung thời gian được tạo lâu (1 năm, 3 tháng, chỉ phụ thuộc vào kích thước của bảng âm thanh).


Có vẻ như đề xuất của bạn là đáng chú ý cho @coneybeare. +1 từ tôi là tốt.
RolandoMySQLDBA

Chỉ mục được tạo không tắt bất cứ lúc nào. Dưới đây là bản cập nhật GIẢI THÍCH. cloud.coneybeare.net/6xvc
coneybeare

2

Nếu đây phải là một truy vấn có sẵn "nhanh chóng" , thì điều đó giới hạn các tùy chọn của bạn một chút.

Tôi sẽ đề nghị phân chia và chinh phục cho vấn đề này.

--
-- 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";

dường như @coneybeare đã thấy điều gì đó trong đề xuất của bạn. +1 từ tôi !!!
RolandoMySQLDBA

Tôi thực sự không thể làm điều này để làm việc. Tôi đã nhận được lỗi sql mà tôi không chắc chắn về cách tiếp cận. Tôi chưa bao giờ thực sự làm việc với các bảng tạm thời
coneybeare

Cuối cùng tôi đã nhận được nó (tôi phải thêm TỪ sounds, ratingsvào truy vấn giữa), nhưng nó đã khóa hộp sql của tôi và tôi phải giết tiến trình.
coneybeare

0

Sử dụng THAM GIA, không truy vấn con. Có bất kỳ nỗ lực truy vấn phụ của bạn giúp đỡ?

HIỂN THỊ TẠO BẢNG âm thanh \ G

HIỂN THỊ TẠO BẢNG xếp hạng \ G

Thường thì có lợi khi có các chỉ mục "gộp", không phải là các cột đơn. Có lẽ INDEX (loại, created_at)

Bạn đang lọc trên cả hai bảng trong THAM GIA; đó có thể là một vấn đề hiệu suất.

Có khoảng 1500 âm thanh và 2 triệu xếp hạng.

Đề nghị bạn có id auto_increment ratings, xây dựng bảng tóm tắt và sử dụng id AI để theo dõi nơi bạn "rời đi". Tuy nhiên, không lưu trữ trung bình trong bảng tóm tắt:

avg (xếp hạng.rating) NHƯ avg_rating,

Thay vào đó, hãy giữ SUM (xếp hạng.rating). Trung bình của trung bình là không chính xác về mặt toán học để tính trung bình; (tổng của tổng) / (tổng số) là chính xác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.