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 ~ 50 ミリ秒かかる一部のクエリ (項目テーブルの更新) が、50,000 ミリ秒以上になることがあるのはなぜですか?

4

1 に答える 1

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インデックスを使用してから、itemsテーブル全体をスキャンし、where句でフィルタリングしてから、カテゴリテーブルに結合する必要があります。これは、主キー(categories.id)のインデックスシークを意味します。 )アイテム結果セットのアイテム行ごとのインデックス。次に、名前でグループ化して(文字列の比較を使用して)カウントし、10個を除くすべての結果を削除します。

私は次のようなクエリを書きます:

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          

(構文が完全でない場合は申し訳ありませんが、MySQLを実行していません)

このクエリでは、エンジンがおそらく行うことは次のとおりです。

items.stateインデックスを使用して、「listed」、「reserved」アイテムを取得し、category_idでグループ化して、文字列ではなく数値を比較し、上位10個のカウントのみを取得してから、カテゴリに結合して名前を取得します(ただし、10個のインデックスシークのみを使用します) 。

于 2012-09-27T00:32:40.047 に答える