MySQL 查询/表需要优化
Posted
技术标签:
【中文标题】MySQL 查询/表需要优化【英文标题】:MySQL Query/Table in need of optimization 【发布时间】:2019-01-29 15:21:37 【问题描述】:我有一个查询需要很长时间。 〜7分钟尴尬。我真的很感激一些帮助。缺少索引?重写查询?以上都是?
非常感谢
mysql Ver 14.14 Distrib 5.7.25,适用于 Linux (x86_64)
查询看起来像:
SELECT COUNT(*) AS count_all, name
FROM api_events ae
INNER JOIN products p on p.token=ae.product_token
WHERE (ae.created_at > '2019-01-21 12:16:53.853732')
GROUP BY name
这是两个表定义
api_events 拥有约 31 百万条记录
CREATE TABLE `api_events` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`api_name` varchar(200) NOT NULL,
`hostname` varchar(200) NOT NULL,
`controller_action` varchar(2000) NOT NULL,
`duration` decimal(12,5) NOT NULL DEFAULT '0.00000',
`view` decimal(12,5) NOT NULL DEFAULT '0.00000',
`db` decimal(12,5) NOT NULL DEFAULT '0.00000',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`product_token` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `product_token` (`product_token`)
) ENGINE=InnoDB AUTO_INCREMENT=64851218 DEFAULT CHARSET=latin1;
和 products只有 12 条记录
CREATE TABLE `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(30) NOT NULL,
`name` varchar(100) NOT NULL,
`description` varchar(2000) NOT NULL,
`token` varchar(50) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=latin1;
【问题讨论】:
为什么是token varchar(50)和product_token varchar(255)? 我建议在 created_at 上为 api_events 表使用范围分区。欲了解更多详情,请查看 - https://dev.mysql.com/doc/refman/8.0/en/partitioning-range.html 【参考方案1】:添加索引可以提高连接性能
create index idx1 on api_events(product_token, created_at);
create index idx2 on products(token);
您也可以尝试反转 api_events 的列
create index idx1 on api_events(created_at, product_token);
并尝试为产品索引添加冗余
create index idx2 on products(token, name);
【讨论】:
您为什么不建议使用外键而不是两个索引,这是有原因的吗?编辑:没关系,我只是看到MySql在添加外键时似乎没有隐式添加索引。 @FlorianLim 添加约束 id 是一种设计选择...如果性能选择则添加索引 .. 所以在这种情况下,我建议使用索引 .. 部分 @scaisEdge 新索引极大地提高了我现在大约 17 秒的性能......好得无法估量,但仍然很长,是否和我预期的一样好? 我已经用一些建议更新了答案.. .. 我不知道您在两个表中有多少行以及服务器的功能。 .. 但是如果建议没有进一步改进,那么最后的更改是硬件功能和数据库存储调整(肯定更复杂的索引)【参考方案2】:对于上述查询,您需要
api_events: INDEX(created_at, product_token)
products: INDEX(token, name)
因为WHERE
提到了api_events,所以优化器很可能会从那个表开始。 created_at
在 WHERE
中,因此索引以它开头,即使以“范围”开头通常是错误的。在这种情况下,这对是“覆盖”。
那么,INDEX(token, name)
也在“覆盖”。
“覆盖”索引提供了少量但差异很大的性能改进。
【讨论】:
【参考方案3】:如果您按令牌而不是 name
分组会发生什么?
SELECT ae.product_token, COUNT(*) AS count_all
FROM api_events ae
WHERE ae.created_at > '2019-01-21 12:16:53.853732')
GROUP BY ae.product_token;
对于这个查询,api_events(created_at, product_token)
上的索引可能会有所帮助。
如果这样更快,那么您可以使用子查询来引入名称。
【讨论】:
【参考方案4】:似乎created_at
的标准非常有选择性(仅查看过去 7 天?)。这迫切需要探索以created_at
作为前导列的索引。
查询还引用了同一表中的product_token
列,因此我们可以将该列包含在索引中,使其成为覆盖索引。
api_events_IX ON api_events ( created_at, product_token )
使用该索引,我们可以避免查看 3100 万行中的绝大多数,并快速缩小我们实际需要查看的行子集。
使用索引,查询仍然需要“使用文件排序”操作来满足 GROUP BY。
(我在这里的猜测是连接到产品中的 12 行并没有排除很多行......在api_event
中的绝大多数行上,product_token
指的是存在于product
.
使用 MySQL EXPLAIN
查看查询执行计划。
进一步可能的改进(测试性能)是在内联视图中进行一些聚合:
SELECT SUM(s.count_all) AS count_all
, p.name
FROM ( SELECT COUNT(*) AS count_all
, ae.product_token
FROM api_events ae
WHERE ae.created_at > '2019-01-21 12:16:53.853732'
GROUP
BY ae.product_token
) s
JOIN products p
ON p.token = s.product_token
GROUP
BY p.name
如果关于product_token
的假设是错误的,如果api_event
中有很多行具有product_token
值而没有引用product
中的行......我们可能会采取不同的策略。 ..
【讨论】:
以上是关于MySQL 查询/表需要优化的主要内容,如果未能解决你的问题,请参考以下文章