我们的团队刚刚花了上周调试并试图找到许多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毫秒及更高?
答案 0 :(得分: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个)指数寻求)。