2016-12-08 5 views
1

と範囲を使用して、MySQLのテーブルは、以下の2つのテーブルのテーブル(簡略化)を含む:各package_id/for_date複合キー

(~13000)   (~7000000 rows)  
---------------  -------------------- 
| packages |  | packages_prices | 
---------------  -------------------- 
| id (int) |<- ->| package_id (int) | 
| state (int) |  | variant_id (int) | 
- - - - - - -  | for_date (date) | 
        | price (float) | 
        - - - - - - - - - 

の組合せはわずか数(平均3)変種有します。 stateは0(非アクティブ)または1(アクティブ)です。 13000人のうち4000人がアクティブです。

まず私はちょうど価格セット(関係なく、変動の)を持っているパッケージを知りたいので、私は(1)for_dateをカバーする複合キーを追加し、(2)pidと私は照会:

select distinct package_id from packages_prices where for_date > date(now()) 

このクエリは3500行を返すのに1秒かかりますが、これは多すぎます。 Explainは複合キーがkey_len 3と一緒に使用され、2000000行が100%フィルタリングされて型範囲で検査されることを示しています。 Using where; Using index; Using temporary。この区別は3500行に戻ります。

distinctを取り除くと、Using temporaryは言及されなくなりますが、クエリは1000000行を返しますが、依然として1秒かかります。

質問1:なぜ、このクエリは非常に遅く、私はテーブルの列を追加または変更することなく、それをどのようにスピードアップできますか?コンポジットキーを考えると、このクエリのコストは0,01未満になるはずです。

今私はアクティブの価格が設定されているパッケージを知りたいと思っています。

私はstateにキーを追加し、上記のように逆の順序で新しい複合キーを追加します。

select distinct packages.id from packages 
inner join packages_prices on id = package_id and for_date > date(now()) 
where state = 1 

クエリには2秒かかります。説明はpackagesテーブルについて教えてください。stateのキーはkey_len 4で使用され、4000行が検査され、100%タイプのタイプrefがフィルタされます。 Using index; Using temporarypackages_pricesテーブルでは、新しい複合キーがkey_len 4で使用され、1000行が検査され、タイプrefで33.33%のフィルタリングが行われます。 Using where; Using index; Distinct。差別化はそれを3000行に戻す。

distinctを取り除くと、Using temporaryDistinctにはもう言及されませんが、クエリは850000行を返し、3秒かかります。

質問2:なぜクエリがそれほど遅いのですか? Explainに基づいて範囲が使用されなくなったのはなぜですか?なぜ新しいコンポジットキーを使用したフィルタリングが33.33%に下がったのですか?私はコンポジットキーが再び100%予兆をフィルタリングすることを期待していました。

これはすべて基本的で些細なようですが、時間がかかり、時間がかかりましたが、実際に何が起こっているのかまだ分かりません。

+0

まず、2つに分けて質問してください。それから、テーブルの作成、索引の作成、完全な説明計画の表示をしてください。 –

+0

あなたが '異形 'と言ったとき、あなたは異なる' packages_prices'を意味しますか? –

+0

@HoneyBadgerこれらは2つの別々のテーブルです。しかし、彼はそれを並べて置く。 –

答えて

1

あなたの所見は、MySQLの仕組みと一貫しています。あなたの最初のクエリでは、インデックス(for_date, package_id)を使用して、指定された日付(インデックスを使用してその位置を見つける)からMySQLが開始されますが、次のエントリごとに未知のpackage_idが表示されるため、インデックスの最後に移動する必要があります。具体的には、package_idが挙げられる。最新のfor_dateで使用されています。この検索で​​は、2000000行の検査行が追加されます。関連するデータは索引から取り出されますが、それにはまだ時間がかかります。

どうすればよいですか?いくつかの創造的な書き換えを使用すると、以下のコードにクエリを変換することができ

select package_id from packages_prices 
group by package_id 
having max(for_date) > date(now()); 

それはあなたの最初のクエリとしてあなたに同じ結果を与える:それをすることになる少なくとも1 for_date > date(now())が(存在する場合あなたの結果セットの一部)は、max(for_date)でもそうです。しかし、これはpackage_idmax(for_date)を持つもの)ごとに1つの行をチェックするだけでよく、for_date > date(now())の他のすべての行はスキップできます。

MySQLはusing index for group-by -optimization(そのテキストはexplainに表示されるはずです)によって行います。インデックスが(package_id, for_date)(すでに持っている)を必要とし、13000行だけを調べる必要があります。リストが順序付けされているので、package_idの最後のエントリに直接ジャンプすることができ、値はmax(for_date)です。次にpackage_idに進みます。

実際には、この方法を使用してdistinctを最適化することができます(そして、for_dateの条件を削除した場合はおそらくそうなります)。しかし、常に方法を見つけることはできません。本当に巧妙なオプティマイザは私がやったのと同じ方法でクエリを書き直すことができましたが、私たちはまだそこにいません。

データの配信に応じて、この方法は悪い考えです。 7000000 package_idですが、将来は20個しか表示されず、for_dateの各数値をpackage_idにチェックすると、インデックスで簡単に見つかる20行を確認するよりもはるかに遅くなります。for_dateだからあなたのデータに関する知識は、より良い(そしておそらく最適な)戦略を選択する上で重要な役割を果たすでしょう。

同じ方法で2番目のクエリを書き換えることができます。残念なことに、そのような最適化は、必ずしも特定のクエリや状況に特有のものではない場合があります。別の配信(前述のとおり)を行っている場合や、クエリを少し変更して終了日を追加すると、そのメソッドはもう機能しなくなり、別のアイデアを考え出す必要があります。