2012-09-26 11 views
12

私たちのチームは、先週のデバッグに費やして、多くのmysqlロックタイムアウトや非常に長い実行クエリのソースを見つけようとしました。結局、このクエリが原因であるようです。なぜこのクエリはロック待ちタイムアウトを引き起こしますか?

mysql> explain 

SELECT categories.name AS cat_name, 
COUNT(distinct items.id) AS category_count 
FROM `items` 
INNER JOIN `categories` ON `categories`.`id` = `items`.`category_id` 
WHERE `items`.`state` IN ('listed', 'reserved') 
    AND (items.category_id IS NOT NULL) 
GROUP BY categories.name 
ORDER BY category_count DESC 
LIMIT 10\G 

*************************** 1. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: items 
     type: range 
possible_keys: index_items_on_category_id,index_items_on_state 
      key: index_items_on_category_id 
     key_len: 5 
      ref: NULL 
     rows: 119371 
     Extra: Using where; Using temporary; Using filesort 
*************************** 2. row *************************** 
      id: 1 
    select_type: SIMPLE 
     table: categories 
     type: eq_ref 
possible_keys: PRIMARY 
      key: PRIMARY 
     key_len: 4 
      ref: production_db.items.category_id 
     rows: 1 
     Extra: 
2 rows in set (0.00 sec) 

厄介なテーブルスキャンを実行し、実行する一時テーブルを作成していることがわかります。

なぜこのクエリはデータベース応答時間を10倍に増加させ、通常は40-50ms(アイテムテーブルの更新)を要するクエリを50,000ms以上に爆発させるのでしょうか?

+1

*「distinct」なしで*プロファイリングを試しましたか?これを行うにはかなりの労力がかかります。フィルタリングするにはかなりの行があります:) – PhD

+0

非常に良い。 Nopeはそれをしなかった。間違いなくそれを最適化するのに役立ちます。このような遅いクエリがなぜ私たちに多くの問題を引き起こすのかについてはっきりしていません。 – chrishomer

+0

これはなぜ 'IN(item.category_id IS NOT NULL)'が必要なのか不思議です - これは 'INNER JOIN'です - category.idは' NULL 'にすることができます –

答えて

5

  1. などのようなより多くの情報なしで伝えるのは難しいですトランザクション内で実行されているということですか?
  2. もしそうなら、分離レベルとは何ですか?
  3. いくつのカテゴリがありますか?
  4. いくつのアイテムがありますか?

私の推測では、クエリが遅すぎるとは(あなたはこの問題を持っているので、それはおそらくです) トランザクション内で実行されていると は、おそらくこれができない項目のテーブルの上にレンジ・ロックを発行しているということでしょう の書き込みが進行して、テーブル上のロック を取得できるまで更新が遅くなるようにしてください。

そして私は、私はあなたのクエリと実行プランから見ることができるものに基づいて、コメントのカップルがあります:

1)あなたのitems.state おそらく代わりに有していると、カタログとして良いだろうこれはスペース効率のためであり、IDの比較は文字列を比較するよりも高速です(エンジンの最適化にかかわらず)。

2)私はitems.stateが基数が低い(一意の値が少ない)列であることを推測しています。したがって、その列のインデックスがおそらくあなたを援助する以上の犠牲になっています。索引を追加する必要があるため、すべての索引は行の挿入/削除/更新時にオーバーヘッドを追加しますが、この特定の索引はおそらくそれほど価値があるとは言えません。もちろん、私はちょうど推測している、それは残りのクエリに依存する。

SELECT 
    ; Grouping by name, means comparing strings. 
    categories.name AS cat_name, 
    ; No need for distinct, the same item.id cannot belong to different categories 
    COUNT(distinct items.id) AS category_count 
FROM `items` 
INNER JOIN `categories` ON `categories`.`id` = `items`.`category_id` 
WHERE `items`.`state` IN ('listed', 'reserved') 
    ; Not needed, the inner join gets rid of items with no category_id 
    AND (items.category_id IS NOT NULL) 
GROUP BY categories.name 
ORDER BY category_count DESC 
LIMIT 10\G 

このクエリは、基本的に指数がオンシーク意味カテゴリテーブル、との接合、次いで、そのはCATEGORY_IDインデックスを使用しているので、その後、全体のアイテムテーブルをスキャンする必要がwhere句によってフィルタリングされ構成されている方法items結果セット内のアイテム行ごとの主キー(categories.id)インデックス。その後、名前でグループ化して(文字列比較を使用して)カウントし、結果のうち10個を除くすべてを取り除きます。

私は次のようにクエリを記述します。エンジンはおそらく何をするか、このクエリでは(構文は、私はMySQLを実行しているわけではない完璧ではない場合、私は申し訳ありません)

SELECT categories.name, counts.n 
FROM (SELECT category_id, COUNT(id) n 
     FROM items 
     WHERE state IN ('listed', 'reserved') AND category_id is not null 
     GROUP BY category_id ORDER BY COUNT(id) DESC LIMIT 10) counts 
JOIN categories on counts.category_id = categories.id 
ORDER BY counts.n desc   

を:

項目を使用します。リストされたアイテムを取得し、category_idでグループ化し、数字を比較して文字列ではなく、最上位の10個のカウントだけを取得し、次にカテゴリに参加して名前を取得します(しかし、10個のインデックスシークのみを使用します)。

関連する問題