web-dev-qa-db-ja.com

EXPLAINプランに基づいてMySQLクエリを最適化する方法

クエリのEXPLAIN計画を見て、最適化を行うのに最適な場所をどのように決定しますか?

最初にチェックすることの1つは、適切なインデックスが使用されているかどうかですが、それを超えると少し困惑します。過去の試行錯誤の結果、結合の順序が良い改善の源になることがありましたが、実行計画を見てどうやってそれを判断できるのでしょうか。

クエリを最適化する方法についての一般的な理解を深めたいと思いますが(読むことをお勧めします)、抽象的な話をするよりも具体的な事例について話し合う方が簡単な場合が多いことも理解しています。私は現在これで壁に頭をぶつけているので、あなたの考えは大いにありがたいです:

 idselect_typeテーブルタイプpossible_keyskey key_len ref rows Extra 
 1 SIMPLE S const PRIMARY、l、p、f4 PRIMARY 2 const1一時的な使用
 1 SIMPLE Q ref PRIMARY、SS 2 const204インデックスの使用
 1 SIMPLE V ref PRIMARY、n、QQ 5 const、db.Q.QID6使用場所;インデックスの使用; Distinct 
 1 SIMPLE R1 ref PRIMARY、L L 154 const、db.V.VID447インデックスを使用。 Distinct 
 1 SIMPLE W eq_ref PRIMARY、w PRIMARY 5 const、db.R.RID、const1使用場所; Distinct 
 1 SIMPLE R2 eq_ref PRIMARY、L PRIMARY 156 const、db.W.RID、const 1 where;明確

実行プランの最終行を次のように解釈するのは正しいですか?

  • 主キーで完全に一致するため、出力行ごとに1行の_R2_のみをフェッチする必要があります。
  • ただし、そのような出力行は、_R2_に適用されるいくつかの基準に基づいてフィルタリングされますか?

もしそうなら、私の問題はその最後のステップで発生するフィルタリングにあります。条件によってフィルタリングが行われない場合(例:WHERE `Col_1_to_3` IN (1,2,3))、クエリは非常に高速に実行されます(〜50ms)。ただし、条件によって選択された行が制限されている場合(WHERE `Col_1_to_3` IN (1,2))、クエリにはかなり長い時間がかかります(〜5秒)。制限が単一の一致(WHERE `Col_1_to_3` IN (1))に対するものである場合、オプティマイザーはまったく異なる実行プランを提案します(5秒よりわずかに優れていますが、50ミリ秒よりはるかに劣っています)。そのテーブルで使用できるより良いインデックスがあるようには見えません(結果ごとに1行を返すために主キーをすでに完全に使用している場合)。

このすべての情報をどのように解釈する必要がありますか?このような出力フィルタリングは結合される最終テーブルで行われるため、テーブルを早く結合してそのような行をより早くフィルタリングするのに比べて、かなりの労力が無駄になると私は推測していますか?もしそうなら、実行計画の_R2_をいつ参加させるべきかをどのように決定しますか?

ここにクエリとスキーマを完全に含めることには抵抗しましたが(単に答えを言われるだけでなく、何を探すべきかを本当に知っている可能性が高いため)、議論を進める必要があることを理解しています。

_SELECT DISTINCT
    `Q`.`QID`
FROM
    `S`
    NATURAL JOIN `Q`
    NATURAL JOIN `V`
    NATURAL JOIN `R` AS `R1`
    NATURAL JOIN `W`

    JOIN `R` AS `R2` ON (
            `R2`.`SID` = `S`.`SID`
        AND `R2`.`RID` = `R1`.`RID`
        AND `R2`.`VID` = `S`.`V_id`
        AND `R2`.`Col_1_to_3` IN (1,2) -- this is where performance suffers!
    )

WHERE
    AND `S`.`SID` = @x
    AND `W`.`WID` = @y
;
_

テーブルRの定義は次のとおりです。

_CREATE TABLE `R` (
  `SID` smallint(6) unsigned NOT NULL,
  `RID` smallint(6) unsigned NOT NULL,
  `VID` varchar(50) NOT NULL DEFAULT '',
  `Col_1_to_3` smallint(1) DEFAULT NULL,
  `T` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`SID`,`RID`,`VID`),
  KEY `L` (`SID`,`VID`,`Col_1_to_3`),
  CONSTRAINT `R_f1` FOREIGN KEY (`SID`) REFERENCES `S` (`SID`),
  CONSTRAINT `R_f2` FOREIGN KEY (`SID`, `VID`) REFERENCES `V` (`SID`, `VID`),
  CONSTRAINT `R_f3` FOREIGN KEY (`SID`, `VID`, `Col_1_to_3`) REFERENCES `L` (`SID`, `VID`, `LID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
_
21
eggyal

目的とクエリの内容によって異なります。

一般に、Using whereを持つEXPLAINのすべての行について、インデックス(possible keysおよびkeys列)を使用する必要があります。これらはフィルターであり、WHEREとONが含まれます。 Using indexと言わせるとさらに良いです。これは、カバーするインデックスがあり、MySQLがテーブルデータの行にアクセスするのではなく、インデックスから直接データを取得できることを意味します。

Using whereがなく、多数の行を返している行を確認する必要があります。これらは、テーブル内のすべての行の値を返します。私はあなたの質問が何であるかわからないので、ここで警戒するべきかどうかわかりません。結果セットをフィルタリングして、サイズを縮小し、パフォーマンスを向上させてください。

通常、Using filesortまたはUsing temporaryは表示されないようにする必要がありますが、これらは予期しない場合にのみ問題になります。

Filesortは通常、ORDER句とともに表示されます。通常、MySQLでカバーインデックス(Using index)を使用して、サーバーから行が既に順番に返されるようにします。そうでない場合、MySQLはfilesortを使用して後でそれらを注文する必要があります。

Using temporaryは、派生テーブルを参照する場合、インデックスがないために問題になる可能性があります。インデックスを使用して一時テーブルを明示的に作成したようですので、ここでは悪くありません。場合によっては、派生テーブルを使用することしか選択できないため、Using temporaryになります。

15
Marcus Adams