2012-09-26 110 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,000毫秒或更高?

+1

您是否嘗試過分析*而沒有*'distinct'?這需要相當多的工作,並且你有相當多的行來過濾:) – PhD

+0

非常好。 Nope沒有這樣做。它絕對有助於優化它。仍然不清楚爲什麼這樣一個緩慢的查詢會導致我們很多問題。 – chrishomer

+0

只是想知道爲什麼你需要這個'AND(items.category_id不是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子句過濾,然後,與該類別表,這意味着索引查找上接合的方式項目結果集中每個項目行的主鍵(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)

與此查詢什麼引擎可能會做的是:

使用這些項目。通過category_id比較數字,而不是字符串,然後只獲得10個最高計數,然後加入類別以獲得名稱(但僅使用10個索引查找)來獲得「列出的」,「保留的」項目和組。

相關問題