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_atWHERE 中,因此索引以它开头,即使以“范围”开头通常是错误的。在这种情况下,这对是“覆盖”。

那么,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 查询/表需要优化的主要内容,如果未能解决你的问题,请参考以下文章

170727MySQL查询性能优化

MySQL - 添加多个派生表时查询慢 - 优化

MySQL5.7性能优化系列——SQL语句优化——使用物化策略优化子查询

mysql 查询优化规则

优化一个非常大的 mysql 表(查询或 mysql)

「mysql优化专题」单表查询优化的一些小总结,非索引设计