web-dev-qa-db-ja.com

MySQL Explainはスロークエリログとは異なる行数を持っています

スロークエリログにこのエントリがあります。

# User@Host: user[Host] @  [ip]
# Thread_id: 1514428  Schema: db  Last_errno: 0  Killed: 0
# Query_time: 2.795454  Lock_time: 0.000116  Rows_sent: 15  Rows_examined: 65207  Rows_affected: 0  Rows_read: 65207
# Bytsent: 26618
SET timestamp=1407511874;

select off.*,translated_title,translated_description 
from ephpb2b_products off  USE INDEX(id_viewed)  
  INNER JOIN ephpb2b_members mem 
    ON  off.uid = mem.id 
  Left Join ephpb2b_product_language_new pol 
    ON  off.id = pol.offer_id                                         
    and pol.language='en'
where off.approved=1 
order by off.viewed  
LIMIT 15; 

このクエリについて説明すると、まったく問題ありません。

mysql> explain select off.*,translated_title,translated_description from ephpb2b_products off  USE INDEX(id_viewed)  INNER JOIN ephpb2b_members mem ON off.uid = mem.id Left Join ephpb2b_product_language_new pol ON off.id = pol.offer_id and pol.language='en' where off.approved=1 order by off.viewed  LIMIT 15;

+----+-------------+-------+--------+-------------------------+-------------+---------+---------------------------+------+-------------+
| id | select_type | table | type   | possible_keys           | key         | key_len | ref                       | rows | Extra       |
+----+-------------+-------+--------+-------------------------+-------------+---------+---------------------------+------+-------------+
|  1 | SIMPLE      | off   | index  | NULL                    | id_viewed   | 4       | NULL                      |    3 | Using where |
|  1 | SIMPLE      | mem   | eq_ref | PRIMARY                 | PRIMARY     | 4       | db.off.uid |    1 | Using index |
|  1 | SIMPLE      | pol   | ref    | offer_id,id_language | offer_id | 5       | db.off.id  |    4 |             |
+----+-------------+-------+--------+-------------------------+-------------+---------+---------------------------+------+-------------+
3 rows in set (0.17 sec)

このクエリを最適化するにはどうすればよいですか?なぜ3行が表示され、クエリログが遅いので65207行を調べたと表示されるのですか。

5
codefreak

皆さんの洞察に感謝します。この問題は、このクエリを2つの異なるクエリに分割することで解決しました。最初にIDを照会し、次にそれらのIDを他のテーブルに渡して情報を得ました。

select id from ephpb2b_products off INNER JOIN ephpb2b_members mem 
    ON  off.uid = mem.id 
where off.approved=1 
order by off.viewed  
LIMIT 15;

次に:

select * from ephpb2b_product_language_new where offer_id IN ({ids from lasts query})

これははるかに優れたパフォーマンスを発揮し、奇妙な動作をしません。

0
codefreak

この質問に答えるには、説明の行列の意味、および統計に基づく計算と実行後の統計の違いを理解する必要があります。

Explainを実行すると、行の列から、目的のフィルターを使用して検査される行数が表アクセスごとにわかります。これを計算する方法は2つあります。インデックスダイブ(通常は正確な結果が得られるはずです)と、各エンジンが個別に保存するおおよその統計を使用する方法-各テーブルで最大5.6-。最初の方法を使用できる場合は1つの方法が推奨されますが(単一のインデックス付き列に対する単純なフィルター)、多くの場合、近似しか使用できません。そうでない場合、クエリオプティマイザーはクエリの実行自体と同じだけの時間を要します。

いずれの場合も、行はテーブルアクセスごとに読み取られる(返されない)計算された行です。正確であっても(多くの場合、数桁の差がありますが、オプティマイザには十分です)、結合全体でアクセスされる実際の行数は予測されません。たとえば、テーブルA(正確にX行を読み取る)とテーブルB(正確にY行を読み取る)を_A -> B_、読み取られる実際の行数は次のようになります:X + # of rows returned by A (<=X) multiplied by Y、標準のmysqlはネストされたループ結合のみをサポートするため。

これらの統計は実行後に収集されるため、正確なのでであるため、ハンドラーの統計やその他のプロファイリングメカニズムのような遅いログは、処理および送信された実際の行数を示します。

あなたの特定のケースに関して、EXPLAINは、実際にはフルインデックススキャンを実行している可能性がある場合に、最初のアクセスでスキャンされるのは3行のみであることを示しているためです(ソートにのみキーを使用しているため) )。これは、実行される結合ごとに後で乗算されます。 説明を信用しないでください。以下を使用できます。

_FLUSH STATUS;
-- Execute your query here
SHOW STATUS like 'Hand%';
_

行操作の実際の数と種類(PKアクセス、参照、インデックススキャン、テーブルスキャン)を確認します。各テーブルアクセスを個別にテストするために使用します。

より具体的なヘルプを得るには、各テーブルのテーブル構造と各フィルター条件のおおよその選択性が必要になります。

8
jynus

EXPLAIN、低速のクエリログ、および検査対象の行に関する説明については、+ 1から@junusまで。

"クエリを最適化する方法"について:

productsテーブルとmembersテーブルの間に明示的な外部キー関係があると仮定すると、それらの間の結合は次のようになります。

_ephpb2b_products off  
  INNER JOIN ephpb2b_members mem 
    ON  off.uid = mem.id 
_

_LEFT JOIN_に変換できます。 FKは、2つのクエリが100%等価であることを保証します。それを念頭に置き、where句とorder句は次のとおりです。

_WHERE off.approved=1
ORDER BY off.viewed  
LIMIT 15
_

ベーステーブル(products)の列のみを使用します。つまり、FROM句の「左側」の部分のテーブルを使用します。サブクエリを使用して、最初に行を制限し、もう一方を結合できます。 2つのテーブル、私が呼ぶテクニック
"最初にlimit、次にjoin"

_SELECT 
    off.*, 
    pol.translated_title, 
    pol.translated_description 
FROM
    ( SELECT p.*                                 -- first limit
      FROM ephpb2b_products AS p
      WHERE p.approved=1
      ORDER BY p.viewed  
      LIMIT 15  
    ) AS off 
  LEFT JOIN                                      -- then join
    ephpb2b_members AS mem 
      ON  off.uid = mem.id 
  LEFT JOIN 
    ephpb2b_product_language_new AS pol 
      ON  pol.language = 'en'                                         
      AND pol.offer_id = off.id   
ORDER BY 
    off.viewed ;                         -- no LIMIT required here 
_

ephpb2b_products (approved, viewed)のインデックスを使用すると、サブクエリは非常に効率的になります。 15行のみが含まれるため、残りの実行プランは問題になりません(結合する列にインデックスがあると思います)。

phpb2b_product_language_new (language, offer_id)にインデックスを追加すると、効率がさらに向上する可能性があります(ただし、盲目的にこれを追加しないで、最初にテストしてください。上記のインデックスと書き換えで、速度を大幅に向上させることができます)。

2
ypercubeᵀᴹ