あなたの質問、そして以下の戦略はインデックスから利益を得るでしょうON log(device_id,when)
。ON log(device_id)
そのインデックスは冗長になるため、そのインデックスを置き換えることができます。
デバイスごとに大量のログエントリがある場合、クエリのJOINは適切なサイズの中間結果セットを生成し、デバイスごとに1行にフィルターされます。MySQLオプティマイザにその反結合操作の「ショートカット」があるとは思いません(少なくとも5.1では)...しかし、クエリが最も効率的かもしれません。
Q:別の戦略で仕事を終わらせることはできますか?
はい、他にも戦略がありますが、これらのいずれかがクエリよりも「優れている」かどうかはわかりません。
アップデート:
検討する可能性のある戦略の1つは、各デバイスの最新のログエントリを保持する別のテーブルをスキーマに追加することです。これは、テーブルで定義されたTRIGGERによって維持できlog
ます。挿入のみを実行している場合(最新のログエントリのUPDATEおよびDELETEがない場合、これはかなり簡単です。log
テーブルに対して挿入が実行されるたびに、トリガーが起動され、ログテーブルに挿入されている値がAFTER INSERT FOR EACH ROW
比較されます。 when
device_idをテーブルの現在のwhen
値に変更し、log_latest
テーブルの行を挿入/更新して、log_latest
最新の行が常に存在するようにします。また、デバイス名をテーブルに(冗長に)格納することもできます(または、latest_when
とlatest_message
デバイステーブルへの列、およびそれらをそこで維持します。)
しかし、この戦略は元の質問を超えています...しかし、「すべてのデバイスの最新のログメッセージ」クエリを頻繁に実行する必要があるかどうかを検討することは実行可能な戦略です。欠点は、余分なテーブルがあり、log
テーブルへの挿入を実行するとパフォーマンスが低下することです。このテーブルは、元のクエリのようなクエリ、または以下の代替手段を使用して完全に更新できます。
1つのアプローチは、テーブルdevice
とlog
テーブルの単純な結合を実行し、デバイス順および降順で行を取得するクエリですwhen
。次に、メモリ変数を使用して行を処理し、「最新の」ログエントリを除くすべてを除外します。このクエリは余分な列を返すことに注意してください。(この余分な列は、クエリ全体をインラインビューとしてラップすることで削除できますが、追加の列が返される状態で生きることができれば、パフォーマンスが向上する可能性があります。
SELECT IF(s.id = @prev_device_id,0,1) AS latest_flag
, @prev_device_id := s.id AS id
, s.name
, s.message
FROM (SELECT d.id
, d.name
, l.message
FROM device d
LEFT
JOIN log l ON l.device_id = d.id
WHERE d.active = 1
ORDER BY d.id, l.when DESC
) s
JOIN (SELECT @prev_device_id := NULL) i
HAVING latest_flag = 1
SELECTリストの最初の式が実行しているのは、その行のデバイスID値が前の行のデバイスIDと異なる場合は常に、その行を「マーク」することです。HAVING句は、1でマークされていないすべての行を除外します(HAVING句を省略して、その式がどのように機能するかを確認できます)。
(構文エラーについてはテストしていません。エラーが発生した場合はお知らせください。詳しく調べます。デスクチェックで問題ないと表示されます...ただし、パレンまたはコンマを見逃した可能性があります)
(別のクエリでラップすることで、その余分な列を「取り除く」ことができます
SELECT r.id,r.name,r.message FROM (
/* query from above */
) r
(ただし、これはパフォーマンスに影響を与える可能性があります。追加の列を使用できる場合は、パフォーマンスが向上する可能性があります。)
もちろん、最も外側のクエリにORDER BYを追加して、結果セットが必要な方法で順序付けられるようにします。
このアプローチは、多数のデバイスでかなりうまく機能し、ログ内の関連する行は2、3行のみです。そうしないと、(ログテーブルの行数のオーダーで)中間結果セットの巨大な混乱が発生し、一時的なMyISAMテーブルにスピンアウトする必要があります。
アップデート:
基本的にすべての行を取得している場合device
(述語があまり選択されていない場合)、テーブル内のすべてのdevice_idの最新のログエントリを取得し、log
テーブルへの結合を延期することで、パフォーマンスを向上させることができdevice
ます。(ただし、結合を行うためにその中間結果セットでインデックスを使用できないことに注意してください。そのため、パフォーマンスを測定するために実際にテストする必要があります。)
SELECT d.id
, d.name
, t.message
FROM device d
LEFT
JOIN (SELECT IF(s.device_id = @prev_device_id,0,1) AS latest_flag
, @prev_device_id := s.device_id AS device_id
, s.messsage
FROM (SELECT l.device_id
, l.message
FROM log l
ORDER BY l.device_id DESC, l.when DESC
) s
JOIN (SELECT @prev_device_id := NULL) i
HAVING latest_flag = 1
) t
ON t.device_id = d.id
注: inlineビューのORDER BY句のdevice_id
と列の両方に降順を指定します。これは、device_idの降順で行が必要なためではなく、MySQLが「逆」を実行できるようにすることでファイルソート操作を回避できるようにするためです。先頭の列(device_id、when)を持つインデックスに対する「スキャン」操作。when
s
注:このクエリは、中間結果セットを一時的なMyISAMテーブルとしてスプールし、それらにインデックスはありません。したがって、これは元のクエリほどには機能しない可能性があります。
もう1つの戦略は、SELECTリストで相関サブクエリを使用することです。ログテーブルから返される列は1つだけなので、これは非常に簡単に理解できるクエリです。
SELECT d.id
, d.name
, ( SELECT l.message
FROM log l
WHERE l.device_id = d.id
ORDER BY l.when DESC
LIMIT 1
) AS message
FROM device d
WHERE d.active = 1
ORDER BY d.id ASC;
注:id
はテーブル内のPRIMARY KEY(またはUNIQUE KEY)であり、device
余分な行を生成するJOINを実行していないため、句を省略できますGROUP BY
。
注:このクエリは「ネストされたループ」操作を使用します。つまり、device
テーブルから返された行ごとに、(基本的に)個別のクエリを実行して、ログから関連する行を取得する必要があります。ほんのdevice
数行(テーブル上でより選択的な述語で返されるようにdevice
)、および各デバイスのログエントリのボートロードがあれば、パフォーマンスはそれほど悪くはありません。ただし、ログメッセージが数個しかない多くのデバイスの場合、他のアプローチの方がはるかに効率的である可能性が非常に高くなります。)
また、このアプローチでは、SELECTリストに別のサブクエリ(最初のサブクエリと同様)を追加し、LIMIT句をスキップするように変更するだけで、2番目に新しいログメッセージを別の列として返すように簡単に拡張できることに注意してください。最初の行を取得し、代わりに2番目の行を取得します。
, ( SELECT l.message
FROM log l
WHERE l.device_id = d.id
ORDER BY l.when DESC
LIMIT 1,1
) AS message_2
基本的にデバイスからすべての行を取得するには、JOIN操作を使用して最高のパフォーマンスを得る可能性があります。このアプローチの1つの欠点は、デバイスの最新のwhen
値が一致する2つ(またはそれ以上)の行がある場合に、デバイスに対して複数の行を返す可能性があることです。(基本的に、このアプローチは、一意の保証がある場合に「正しい」結果セットを返すことが保証されていlog(device_id,when)
ます。
このクエリをインラインビューとして使用して、次の場合に「最新」の値を取得します。
SELECT l.device_id
, MAX(l.when)
FROM log l
GROUP BY l.device_id
これをログテーブルとデバイステーブルに結合できます。
SELECT d.id
, d.name
, m.messsage
FROM device d
LEFT
JOIN (
SELECT l.device_id
, MAX(l.when) AS `when`
FROM log l
GROUP BY l.device_id
) k
ON k.device_id = d.id
LEFT
JOIN log m
ON m.device_id = d.id
AND m.device_id = k.device_id
AND m.when = k.when
ORDER BY d.id
これらはすべて代替戦略です(これはあなたが尋ねた質問だと思います)が、どちらもあなたの特定のニーズに適しているかどうかはわかりません。(ただし、必要に応じて使用するために、ツールベルトにいくつかの異なるツールを含めることは常に良いことです。)