我们的团队刚刚花了上周调试并试图找到许多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毫秒甚至更高?
没有更多的信息就很难分辨
我的猜测是查询太慢而且它在一个事务中运行(它可能是因为你有这个问题)并且可能在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个索引搜索) .
| 归档时间: |
|
| 查看次数: |
913 次 |
| 最近记录: |