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

chr*_*mer 12 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)
Run Code Online (Sandbox Code Playgroud)

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

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

Fra*_*oto 5

没有更多的信息就很难分辨

  1. 这是在交易中运行吗?
  2. 如果是这样,隔离级别是什么?
  3. 有几个类别?
  4. 有多少项?

我的猜测是查询太慢而且它在一个事务中运行(它可能是因为你有这个问题)并且可能在items表上发出了范围锁,它不能允许写入继续因此减慢更新直到它们可以锁定桌面.

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

1)您items.state 可能是,而不必对项目的每一行字符串作为目录更好,这是空间利用率和比较IDS是不是比较字符串(无论任何优化引擎可能会做)的方式更快.

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
Run Code Online (Sandbox Code Playgroud)

构造此查询的方式基本上是必须扫描整个items表,因为它使用了category_id索引,然后通过where子句进行过滤,然后加入类别表,这意味着对主键的索引搜索(categories.id) )items项结果集中每个项目行的索引.然后按名称分组(使用字符串比较)进行计数,然后除去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          
Run Code Online (Sandbox Code Playgroud)

(如果语法不完善,我很抱歉我没有运行MySQL)

通过此查询,引擎可能会执行的操作是:

使用items.state索引来获取'列出','保留'项和按category_id分组比较数字,而不是字符串然后只获得10个最高计数,然后加入类别以获取名称(但仅使用10个索引搜索) .