이 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합니다. 가까운 시일 내에 이와 같은 질문이 더 나오기를 바랍니다.
RolandoMySQLDBA 1

@coneybeare 새로운 EXPLAIN은 2,008,306 대신 21540 행 (359 X 60) 만 읽는 것처럼 보입니다. 원래 답변에서 제안한 쿼리에 대해 EXPLAIN을 실행하십시오. 그로부터 오는 행 수를보고 싶습니다.
RolandoMySQLDBA

@RolandoMySQLDBA 새로운 Explain은 실제로 인덱스에 적은 수의 행이 있음을 보여 주지만 쿼리 실행 시간은 여전히 ​​약 15 초로 개선되지 않았습니다.
coneybeare

@ coneybeare 쿼리를 미세 조정했습니다. 새 쿼리에서 EXPLAIN을 실행하십시오. 나는 그것을 내 대답에 덧붙였다.
RolandoMySQLDBA

답변:


7

쿼리, 테이블 및 WHERE AND GROUP BY 절을 살펴본 후 다음을 권장합니다.

권장 사항 # 1) 쿼리 리 팩터

세 가지 작업을 수행하도록 쿼리를 재구성했습니다.

  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

방금 카디널리티를 보았습니다. 아야! rateable_id에 대한 카디널리티 1 소년, 나는 바보 같은 느낌!

업데이트 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

EXPLAIN에서 등급이 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 개의 현상금을주었습니다.

또한 다른 쿼리 (마진 결과)를 리팩터링하고 인덱스 (극적인 결과)를 추가했습니다. 나는 upvotes 2 개를 얻었고 그 대답을 받아 들였다.

또 다른 쿼리 challange에 대한 색인을 추가하고 한 번 upvoted

그리고 지금 당신의 질문 입니다.

질문을 리팩토링하는 YouTube 동영상에서 영감을 얻은 것과 같은 모든 질문에 답변하고 싶었습니다.

다시 감사합니다, @coneybeare !!! 나는 포인트 나 찬사를 받아들이는 것이 아니라 가능한 한이 질문에 대답하고 싶었다. 지금, 나는 포인트를 적립 느낄 수 있습니다!


나는 시간을 개선하지 않고 색인을 추가했습니다. 새로운 설명은 다음과 같습니다. cloud.coneybeare.net/6y7c
coneybeare

권장 1에서 쿼리에 EXPLAIN : cloud.coneybeare.net/6xZ2 그것은이 쿼리를 실행 30 초 걸렸다
coneybeare

어떤 이유로 든 구문을 약간 편집해야했습니다 (첫 번째 쿼리 전에 FROM을 추가했으며 AAA 별칭을 제거해야했습니다). 설명은 다음과 같습니다. cloud.coneybeare.net/6xlq 실제 쿼리를 실행하는 데 약 30 초가 걸렸습니다
coneybeare

@RolandoMySQLDBA : 23:55 업데이트 설명 : cloud.coneybeare.net/6wrN 실제 쿼리는 1 분 동안 실행되어 프로세스를 종료했습니다
coneybeare

두 번째 내부 선택은 A 선택 테이블에 액세스 할 수 없으므로 A.id에서 오류가 발생합니다.
coneybeare

3

EXPLAIN 출력에 감사드립니다. 이 진술에서 알 수 있듯이, 시간이 오래 걸리는 이유는 등급 테이블의 전체 테이블 스캔입니다. WHERE 문의 아무것도 2 백만 행을 필터링하지 않습니다.

ratings.type에 색인을 추가 할 수는 있지만, CARDINALITY가 실제로 낮아지고에 대한 행을 계속 스캔 할 것입니다 ratings.

또는 인덱스 힌트 를 사용하여 mysql이 사운드 인덱스를 사용하도록 할 수 있습니다 .

업데이트 :

그것이 있다면 sounds.created행을 필터링 할 가능성이 가장 높은 인덱스를 추가 하고 mysql 쿼리 옵티마이 저가 사운드 테이블 인덱스를 사용하도록 할 것입니다. 오래 생성 된 시간 프레임 (1 년, 3 개월, 사운드 테이블의 크기에 따라 다름)을 사용하는 쿼리에주의하십시오.


귀하의 제안이 @coneybeare에 주목할만한 것 같습니다. 나도 +1
RolandoMySQLDBA

작성된 인덱스는 언제라도 면도하지 않았습니다. 다음은 업데이트 된 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!
RolandoMySQLDBA

나는 실제로 이것을 작동시키지 못했습니다. 접근 방법을 잘 모르는 SQL 오류가 발생했습니다. 나는 실제로 임시 테이블로 작업 한 적이 없다
coneybeare

나는 (내가로부터 추가했다 결국 그것을 얻을했다 sounds, ratings중간 쿼리),하지만 내 SQL 상자에 갇혀 나는 프로세스를 종료했다.
coneybeare

0

서브 쿼리가 아닌 JOIN을 사용하십시오. 하위 쿼리가 도움이 되었습니까?

테이블 만들기 소리 보여주기 \ G

테이블 만들기 등급 표시 \ G

단일 열 인덱스가 아닌 "복합"인덱스를 갖는 것이 종종 유리합니다. 아마도 INDEX (type, created_at)

JOIN에서 두 테이블을 모두 필터링하고 있습니다. 성능 문제 일 수 있습니다.

약 1500 개의 사운드와 2 백만 개의 등급이 있습니다.

auto_increment ID를 설정 ratings하고 요약 테이블을 작성하고 AI ID를 사용하여 "중지 된"위치를 추적하는 것이 좋습니다. 그러나 요약표에 평균을 저장하지 마십시오.

평균 (ratings.rating) AS 평균 _rating,

대신 SUM (ratings.rating)을 유지하십시오. 평균을 계산하는 데 평균의 평균이 수학적으로 부정확합니다. (합의 합) / (횟수의 합)이 정확합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.