为什么此查询会导致锁定等待超时?

时间:2012-09-26 23:49:21

标签: mysql

我们的团队刚刚花了上周调试并试图找到许多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)

我可以看到它正在进行令人讨厌的表扫描并创建一个临时表来运行。

为什么此查询会导致数据库响应时间增加十倍,一些查询通常需要40-50ms(项目表更新),有时会爆炸到50,000毫秒及更高?

1 个答案:

答案 0 :(得分:5)

如果没有像

这样的更多信息,很难说清楚
  1. 这是在交易中运行吗?
  2. 如果是这样,隔离级别是什么?
  3. 有多少个类别?
  4. 多少项?
  5.   

    我的猜测是查询太慢而且在一个内部运行   交易(这可能是因为你有这个问题)并且是   可能会在items表上发出不能允许的范围锁   写入继续因此减慢更新,直到他们可以获得锁定   在桌子上。

    我根据您的查询和执行计划中的内容得到了一些评论:

    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
    

    此查询的结构方式基本上是必须扫描整个items表,因为它使用了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)

    使用此查询引擎可能会执行的操作是:

    使用items.state索引来获取“列出的”,“保留”项目,并按category_id比较数字,而不是字符串,然后只获取10个最高计数,然后加入类别以获取名称(但仅使用10个)指数寻求)。