Bu MySQL sorgusunu nasıl daha fazla optimize edebilirim?


9

Ben (15 + saniye) çalıştırmak için özellikle uzun bir zaman alıyor bir sorgu var ve benim veri seti büyüdükçe sadece zamanla kötüleşiyor. Bunu geçmişte optimize ettim ve endeksler, kod düzeyinde sıralama ve diğer optimizasyonlar ekledim, ancak biraz daha rafine edilmesi gerekiyor.

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

Sorgunun amacı bana sound iden son çıkan seslerin ortalama puanını almak. Yaklaşık 1500 ses ve 2 Milyon derecelendirme var.

Üzerinde birkaç endeksim var 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      |         | 
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+

ve birkaç tanesinde 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      |         | 
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

İşte 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 | 
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+-------------+

Elde edilen sonuçları önbelleğe aldım, bu nedenle site performansı çok önemli değil, ancak önbellek ısıtıcılarımın bu kadar uzun sürmesi nedeniyle çalışması daha uzun sürüyor ve bu bir sorun olmaya başlıyor. Bu bir sorguda çatırtı çok sayı gibi görünmüyor ...

Bunun daha iyi performans göstermesi için daha ne yapabilirim ?


EXPLAINÇıktıyı gösterebilir misiniz ? 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 Bu, bugün benim için çok ilginç bir mücadeleydi !!! Sorunuz için +1. Yakın gelecekte bunun gibi başka soruların da gelmesini diliyorum.
RolandoMySQLDBA

@coneybeare Yeni EXPLAIN, 2.008.306 yerine yalnızca 21540 satırı (359 X 60) okuyor gibi görünüyor. Lütfen cevabımda başlangıçta önerdiğim sorguda EXPLAIN'i çalıştırın. Bundan gelen satır sayısını görmek istiyorum.
RolandoMySQLDBA

@RolandoMySQLDBA Yeni açıklama gerçekten indekse sahip daha az sayıda satır olduğunu gösteriyor, ancak sorguyu yürütme süresi hala yaklaşık 15 saniye, hiçbir gelişme göstermiyor
coneybeare

@coneybeare Sorguyu iyi ayarladım. Lütfen yeni sorgumda EXPLAIN aracını çalıştırın. Cevabıma ekledim.
RolandoMySQLDBA

Yanıtlar:


7

Sorgu, tablolar ve WHERE AND GROUP BY yan tümcelerini inceledikten sonra aşağıdakileri öneririm:

Öneri # 1) Sorguyu Yeniden Düzenleyin

Üç (3) şey yapmak için sorguyu yeniden düzenledim:

  1. daha küçük geçici tablolar oluşturun
  2. Bu geçici tablolarda WHERE yan tümcesini işleyin
  3. Sonuna kadar erteleme

İşte benim önerilen sorgu:

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

Öneri # 2) Sesler tablosunu, WHERE yan tümcesini barındıracak bir dizinle dizinleyin

Bu dizinin sütunları, WHERE yan tümcesinden önce statik değerler ve son olarak hareketli hedef içeren tüm sütunları içerir

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

İçtenlikle hoş bir sürpriz olacağına inanıyorum. Bir şans ver !!!

GÜNCELLEME 2011-05-21 19:04

Az önce kardinaliteyi gördüm. OUCH !!! Rateable_id için 1 kardinalitesi. Evlat, aptal hissediyorum !!!

GÜNCELLEME 2011-05-21 19:20

Belki endeksi yapmak bir şeyler geliştirmek için yeterli olacaktır.

GÜNCELLEME 2011-05-21 22:56

Lütfen şunu çalıştırın:

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

GÜNCELLEME 2011-05-21 23:34

Tekrar düzelttim. Bunu Deneyin Lütfen:

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
;

GÜNCELLEME 2011-05-21 23:55

Tekrar düzelttim. Bunu Deneyin Lütfen (Son Kez):

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;

GÜNCELLEME 2011-05-22 00:12

Vazgeçmekten nefret ediyorum !!!!

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;

GÜNCELLEME 2011-05-22 07:51

EXPLAIN'de derecelendirmelerin 2 milyon satırla geri gelmesi beni rahatsız ediyor. Sonra bana vurdu. Derecelendirme tablosunda rateable_type ile başlayan başka bir dizine ihtiyacınız olabilir:

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

Bu endeksin amacı derecelendirmeleri manipüle eden geçici tabloyu 2 milyondan az olacak şekilde azaltmaktır. Bu geçici tabloyu önemli ölçüde daha küçük (en az yarısı) alabilirsek, sorgunuzda daha iyi bir umut olabilir ve benimki de daha hızlı çalışır.

Bu dizini oluşturduktan sonra, lütfen önerilen orijinal sorgumu tekrar deneyin ve kendinizinkini deneyin:

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

GÜNCELLEME 2011-05-22 18:39: NİHAİ KELİMELER

Saklı yordamda bir sorgu refactored ve şeyleri hızlandırmak hakkında bir soruya cevap yardımcı olmak için bir dizin ekledi. 6 oy aldım, yanıtı kabul ettim ve 200 ödül aldım.

Ayrıca başka bir sorgu (marjinal sonuçlar) yeniden ve bir dizin (dramatik sonuçlar) ekledi. 2 oy aldım ve yanıtı kabul ettim.

Başka bir sorgu sorunu için bir dizin ekledim ve bir kez kaldırıldı

ve şimdi sorunuz .

Bu sorulara (sizinkiler de dahil) tüm soruları yanıtlamak isteyen soruları yeniden düzenleme konusunda izlediğim bir YouTube videosundan ilham alındı.

Tekrar teşekkürler, @coneybeare !!! Bu soruyu sadece puanları veya övgüleri kabul etmekle kalmayıp mümkün olan en geniş şekilde cevaplamak istedim. Şimdi, puan kazandığımı hissediyorum !!!


Dizini ekledim, zamanında iyileşme yok. İşte yeni EXPLAIN: cloud.coneybeare.net/6y7c
coneybeare

Öneri 1'den gelen sorgudaki EXPLAIN: cloud.coneybeare.net/6xZ2 Bu sorguyu çalıştırmak yaklaşık 30 saniye sürdü
coneybeare

Bazı nedenlerden dolayı sözdizimini biraz düzenlemek zorunda kaldım (ilk sorgudan önce bir FROM ekledim ve AAA takma adından kurtulmak zorunda kaldım). İşte EXPLAIN: cloud.coneybeare.net/6xlq Gerçek sorgunun çalışması yaklaşık 30 saniye sürdü
coneybeare

@RolandoMySQLDBA: 23:55 güncellemenizde EXPLAIN: cloud.coneybeare.net/6wrN Gerçek sorgu bir dakikadan fazla sürdü, bu yüzden süreci öldürdüm
coneybeare

İkinci iç seçim A seçim tablosuna erişemez, bu nedenle A.id hata verir.
coneybeare

3

EXPLAIN çıktısı için teşekkürler. Bu ifadeden de anlaşılacağı gibi, bu kadar uzun sürmesinin nedeni derecelendirme tablosundaki tam masa taramasıdır. WHERE deyimindeki hiçbir şey 2 milyon satır filtrelemiyor.

Ratings.type dizinine bir dizin ekleyebilirsiniz, ancak tahminim CARDINALITY çok düşük olacak ve hala birkaç satır taramalısınız ratings.

Alternatif olarak , ses dizinlerini kullanmak için mysql'yi zorlamak için dizin ipuçlarını kullanmayı deneyebilirsiniz .

Güncellenmiş:

Eğer ben olsaydım, sounds.createdbu satırları filtrelemek için en iyi şansı var ve muhtemelen mysql sorgu optimizer sesler tablo dizinleri kullanmak için zorlayacak bir dizin eklemek istiyorum . Sadece uzun zaman dilimleri (1 yıl, 3 ay, sadece sesler tablosunun boyutuna bağlıdır) kullanan sorgulara dikkat edin.


Öneriniz @coneybeare için kayda değer gibi görünüyor. Benden +1.
RolandoMySQLDBA

Oluşturulan endeks hiçbir zaman tıraş olmadı. İşte güncellenmiş EXPLAIN. cloud.coneybeare.net/6xvc
coneybeare

2

Bunun "anında" kullanılabilir bir sorgu olması gerekiyorsa, bu seçeneklerinizi biraz sınırlar.

Bu problem için bölün ve fethedilmesini önereceğim.

--
-- 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 önerinizde bir şey gördü. Benden +1 !!!
RolandoMySQLDBA

Aslında bunu çalıştıramadım. Nasıl yaklaşacağımdan emin olamadığım sql hataları alıyordum. Geçici masalarla hiç çalışmadım
coneybeare

(Ben DAN eklemek zorunda sonunda aldın sounds, ratingsorta sorguya), ama benim sql kutusunu kilitli ve ben süreci öldürmek zorunda kaldı.
coneybeare

0

Alt sorguları değil, JOIN'leri kullanın. Alt sorgularınızdan herhangi biri yardımcı oldu mu?

MASA OLUŞTURMA MASASI sesleri \ G

MASA OLUŞTURMA derecelendirmesini görüntüle \ G

Genellikle "tekli" indekslere değil, "bileşik" indekslere sahip olmak faydalıdır. Belki INDEX (tür, oluşturulan_at)

Bir JOIN içindeki her iki tabloya da filtre uygulıyorsunuz; performans sorunu olması muhtemeldir.

Yaklaşık 1500 ses ve 2 Milyon derecelendirme var.

Bir auto_increment kimliğinizin olmasını, ratingsbir özet tablosu oluşturmanızı ve "kaldığınız" yeri takip etmek için AI kimliğini kullanmanızı öneririz. Ancak, ortalamaları özet tabloda saklamayın:

avg (ratings.rating) AS avg_rating,

Bunun yerine, TOPLA'yı (ratings.rating) saklayın. Bir ortalamanın hesaplanması için ortalamaların ortalaması matematiksel olarak yanlıştır; (toplamların toplamı) / (sayıların toplamı) doğrudur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.