これは別のアプローチです。
このクエリの実行プランでは、統計テーブルのすべての行でSORT操作が必要になるため、このクエリでは、正しい結果を返す他のクエリと同じパフォーマンスの問題が発生します。時間列には述語(制限)がないため、統計テーブルのすべての行が考慮されます。本当に大きなstats
テーブルの場合、これは恐ろしい死を迎える前に、利用可能なすべての一時的なスペースを吹き飛ばします。(以下のパフォーマンスに関するその他の注意事項。)
SELECT r.*
, IFNULL(s.avg_votes,0)
FROM servers r
LEFT
JOIN ( SELECT t.server
, AVG(t.votes) AS avg_votes
FROM ( SELECT CASE WHEN u.server = @last_server
THEN @i := @i + 1
ELSE @i := 1
END AS i
, @last_server := u.server AS `server`
, u.votes AS votes
FROM (SELECT @i := 0, @last_server := NULL) i
JOIN ( SELECT v.server, v.votes
FROM stats v
ORDER BY v.server DESC, v.time DESC
) u
) t
WHERE t.i <= 24
GROUP BY t.server
) s
ON s.server = r.id
このクエリが実行しているのは、統計テーブルをサーバー別に、時間列の降順で並べ替えることです。(インラインビューのエイリアスはu
。)
ソートされた結果セットを使用して、各サーバーの各行に行番号1、2、3などを割り当てます。(インラインビューのエイリアスはt
。)
その結果セットを使用して、行番号が24を超えるすべての行を除外し、votes
各サーバーの「最新の」24行の列の平均を計算します。(インラインビューのエイリアスはs
。)
最後のステップとして、それをserversテーブルに結合して、要求された結果セットを返します。
ノート:
このクエリの実行プランは、テーブル内の多数の行に対してCOSTLYになりstats
ます。
パフォーマンスを向上させるために、いくつかのアプローチをとることができます。
最も単純なのは、述語にstats
テーブルからかなりの数の行を除外することです(たとえばtime
、2日以上前または2週間以上前の値を持つ行)。これにより、「最新の」24行を判別するために、ソートする必要のある行の数が大幅に削減されます。
また、インデックスをオンstats(server,time)
にすると、MySQLがインデックスに対して比較的効率的な「逆スキャン」を実行し、ソート操作を回避できる可能性もあります。
の統計テーブルにインデックスを実装することも検討できます(server,"reverse_time")
。MySQLはまだ降順のインデックスをサポートしていないため、実装は実際には派生値の通常の(昇順の)インデックスになります(たとえば、またはrtime
の降順の値に対して昇順の「逆時間」式です。time
-1*UNIX_TIMESTAMP(my_timestamp)
-1*TIMESTAMPDIFF('1970-01-01',my_datetime)
パフォーマンスを向上させる別のアプローチは、サーバーごとに最新の24行を含むシャドウテーブルを保持することです。stats
「最新の行」がテーブルから削除されないことを保証できれば、これを実装するのが最も簡単です。そのテーブルをトリガーで維持できます。基本的に、行がテーブルに挿入されるたびに、新しい行のがシャドウテーブルのサーバーに格納されている最も早い行よりも遅いstats
かどうかを確認します。遅い場合は、シャドウテーブルの最も早い行を新しい行に置き換えます。 、各サーバーのシャドウテーブルに24行を超えないようにしてください。time
time
そして、さらに別のアプローチは、結果を取得するプロシージャまたは関数を作成することです。ここでのアプローチは、各サーバーをループし、統計テーブルに対して個別のクエリを実行してvotes
、最新の24行の平均を取得し、それらすべての結果をまとめることです。(このアプローチは、結果セットを返すことを可能にするためだけに、巨大な一時セットの並べ替えを回避するための回避策であり、必ずしも結果セットの戻りを非常に高速にするわけではありません。)
LARGEテーブルでのこのタイプのクエリのパフォーマンスの要点は、クエリで考慮される行数を制限し、大きなセットでの並べ替え操作を回避することです。このようにして、このようなクエリを実行します。
補遺
「逆インデックススキャン」操作(stats
ファイルソート操作なしでインデックスを使用して順序付けされた行を取得する)を取得するには、ORDERBY句の両方の式でDESCENDINGを指定する必要がありました。上記のクエリには以前にがあり、MySQLは常に、ヒントORDER BY server ASC, time DESC
を指定する場合でもファイルソートを実行したいと考えていました。FORCE INDEX FOR ORDER BY (stats_ix1)
統計テーブルに少なくとも24の関連する行がある場合にのみ、サーバーの「平均投票数」を返すことが要件である場合は、少し面倒であっても、より効率的なクエリを実行できます。(ネストされたIF()関数の乱雑さのほとんどは、平均に含まれないNULL値を処理することです。NULLでない保証があるvotes
場合、または行を除外する場合は、乱雑さははるかに少なくなります。ここで、votes
はNULLです。)
SELECT r.*
, IFNULL(s.avg_votes,0)
FROM servers r
LEFT
JOIN ( SELECT t.server
, t.tot/NULLIF(t.cnt,0) AS avg_votes
FROM ( SELECT IF(v.server = @last_server, @num := @num + 1, @num := 1) AS num
, @cnt := IF(v.server = @last_server,IF(@num <= 24, @cnt := @cnt + IF(v.votes IS NULL,0,1),@cnt := 0),@cnt := IF(v.votes IS NULL,0,1)) AS cnt
, @tot := IF(v.server = @last_server,IF(@num <= 24, @tot := @tot + IFNULL(v.votes,0) ,@tot := 0),@tot := IFNULL(v.votes,0) ) AS tot
, @last_server := v.server AS SERVER
-- , v.time
-- , v.votes
-- , @tot/NULLIF(@cnt,0) AS avg_sofar
FROM (SELECT @last_server := NULL, @num:= 0, @cnt := 0, @tot := 0) u
JOIN stats v FORCE INDEX FOR ORDER BY (stats_ix1)
ORDER BY v.server DESC, v.time DESC
) t
WHERE t.num = 24
) s
ON s.server = r.id
EXPLAINは、カバーリングインデックスをオンstats(server,time,votes)
にすると、MySQLがファイルソート操作を回避したことを示したため、「逆インデックススキャン」を使用して行を順番に返す必要がありました。カバーするインデックスがなく、'(server、time), MySQL used the index if I included an index hint, with the
FORCE INDEX FOR ORDER BY(stats_ix1)`ヒントのインデックスがないため、MySQLはファイルソートも回避しました。(しかし、私のテーブルの行数は100行未満であるため、MySQLはファイルソート操作の回避にあまり重点を置いていないと思います。)
時間、投票、およびavg_sofar式はコメント化されています(インラインビューでは)としてエイリアスされt
ます。これらは必要ありませんが、デバッグ用です。
クエリの現状では、平均を返すために、サーバーごとに少なくとも24行の統計が必要です。(それは許容できるかもしれません。)しかし、私は一般的に、現在の合計、これまでの合計(tot)、および現在のカウント(cnt)を返すことができると考えていました。
(をに置き換えるWHERE t.num = 24
と WHERE t.num <= 24
、移動平均の動作を確認できます。)
統計に少なくとも24行がない場合の平均を返すには、実際には、numの最大値が24未満の行(サーバーごと)を識別する必要があります。