Mysql 如何优化同时依赖于计数和分组依据的查询?

Mysql 如何优化同时依赖于计数和分组依据的查询?,mysql,sql,performance,Mysql,Sql,Performance,我有一个查询,其目的是统计在不同时期(按月、按季度、按年等)从网站下载了多少音乐作品(曲目)。查询操作表entityusage,entityusage\u file和track 要获取属于特定专辑的曲目的下载次数,我将执行以下查询: select date_format(eu.updated, '%Y-%m-%d') as p, count(eu.id) as c from entityusage as eu inner join entityusage_file as

我有一个查询,其目的是统计在不同时期(按月、按季度、按年等)从网站下载了多少音乐作品(曲目)。查询操作表
entityusage
entityusage\u file
track

要获取属于特定专辑的曲目的下载次数,我将执行以下查询:

select 
    date_format(eu.updated, '%Y-%m-%d') as p, count(eu.id) as c
from        entityusage as eu
inner join  entityusage_file as euf 
        ON  euf.entityusage_id = eu.id
inner join  track as t 
        ON t.id = euf.track_id
where
    t.album_id = '0054a47e-b594-407b-86df-3be078b4e7b7'
        and entitytype = 't'
        and action = 1
group by date_format(eu.updated, '%Y%m%d')
我需要设置
entitytype='t'
,因为entityusage也可以保存其他实体的下载(如果
entitytype='a'
那么整个相册都会被下载,并且
entityusage\u文件
会保存相册在下载时“翻译”成的所有曲目)

此查询需要40-50秒。我尝试优化这个查询已经有一段时间了,但我感觉我用了错误的方法

这是生成报告必须运行的四个类似查询中的一个。报告最好能够在用户等待时完成。现在,我在看3-4分钟。那是一段很长的等待时间

这个查询是否可以通过索引进一步优化,或者我是否需要采取另一种方法来完成这项工作

CREATE TABLE `entityusage` (
  `id` char(36) NOT NULL,
  `title` varchar(255) DEFAULT NULL,
  `entitytype` varchar(5) NOT NULL,
  `entityid` char(36) NOT NULL,
  `externaluser` int(10) NOT NULL,
  `action` tinyint(1) NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `e` (`entityid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `entityusage_file` (
  `id` char(36) NOT NULL,
  `entityusage_id` char(36) NOT NULL,
  `track_id` char(36) NOT NULL,
  `file_id` char(36) NOT NULL,
  `type` varchar(3) NOT NULL,
  `quality` int(1) NOT NULL,
  `size` int(20) NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `file_id` (`file_id`),
  KEY `entityusage_id` (`entityusage_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `track` (
  `id` char(36) NOT NULL,
  `album_id` char(36) NOT NULL,
  `number` int(3) NOT NULL DEFAULT '0',
  `title` varchar(255) DEFAULT NULL,
  `updated` datetime NOT NULL DEFAULT '2000-01-01 00:00:00',
  PRIMARY KEY (`id`),
  KEY `album` (`album_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC;
查询中的
解释提供了以下信息:

+------+-------------+-------+--------+----------------+----------------+---------+------------------------------+---------+----------------------------------------------+
| id   | select_type | table | type   | possible_keys  | key            | key_len | ref                          | rows    | Extra                                        |
+------+-------------+-------+--------+----------------+----------------+---------+------------------------------+---------+----------------------------------------------+
|    1 | SIMPLE      | eu    | ALL    | NULL           | NULL           | NULL    | NULL                         | 7832817 | Using where; Using temporary; Using filesort |
|    1 | SIMPLE      | euf   | ref    | entityusage_id | entityusage_id | 108     | func                         |       1 | Using index condition                        |
|    1 | SIMPLE      | t     | eq_ref | PRIMARY,album  | PRIMARY        | 108     | trackerdatabase.euf.track_id |       1 | Using where                                  |
+------+-------------+-------+--------+----------------+----------------+---------+------------------------------+---------+----------------------------------------------+
这是您的查询:

select date_format(eu.updated, '%Y-%m-%d') as p, count(eu.id) as c
from entityusage eu join
     entityusage_file euf
     on euf.entityusage_id = eu.id join
     track t 
     on t.id = euf.track_id
where t.album_id = '0054a47e-b594-407b-86df-3be078b4e7b7' and
      eu.entitytype = 't' and
      eu.action = 1
group by date_format(eu.updated, '%Y%m%d');
我建议在
track(album\u id,id)
entityusage\u file(track\u id,entityusage\u id)
entityusage(id,entitytype,action)
这是您的查询:

select date_format(eu.updated, '%Y-%m-%d') as p, count(eu.id) as c
from entityusage eu join
     entityusage_file euf
     on euf.entityusage_id = eu.id join
     track t 
     on t.id = euf.track_id
where t.album_id = '0054a47e-b594-407b-86df-3be078b4e7b7' and
      eu.entitytype = 't' and
      eu.action = 1
group by date_format(eu.updated, '%Y%m%d');

我建议在
track(album\u id,id)
entityusage\u file(track\u id,entityusage\u id)
entityusage(id,entitytype,action)

上建立索引,因为GROUP BY操作在一个包含函数的表达式上,MySQL无法使用索引来优化该操作。这将需要一个“使用文件排序”操作

考虑到当前的表定义,我相信Gordon建议的索引是最好的选择。但即使有了这些索引,“高岗”也是
eu
表,它将所有这些行分块并排序

为了获得更合理的性能,您可能需要引入一个“预计算结果”表。为每件事生成计数会很昂贵。。。但是我们可以提前支付这个价格

CREATE TABLE usage_track_by_day
( updated_dt DATE NOT NULL
, PRIMARY KEY (track_id, updated_dt)
)
AS
SELECT eu.track_id
     , DATE(eu.updated) AS updated_dt
     , SUM(IF(eu.action = 1,1,0) AS cnt
  FROM entityusage eu
 WHERE eu.track_id IS NOT NULL
   AND eu.updated IS NOT NULL
 GROUP
    BY eu.track_id
     , DATE(eu.updated)
关于entityusage(跟踪id、更新、操作)的索引
可能有助于提高性能

然后,我们可以针对新的“预计算结果”表编写一个查询,从而更好地获得合理的性能

“预计算结果”表将过时,需要定期刷新


这不一定是解决该问题的最佳解决方案,但它是我们可以在数据仓库/数据集市应用程序中使用的一种技术。这让我们可以通过大量的细节行一次获取计数,然后保存这些计数以便快速访问。

因为GROUP BY操作是在一个包含函数的表达式上进行的,MySQL无法使用索引来优化该操作。这将需要一个“使用文件排序”操作

考虑到当前的表定义,我相信Gordon建议的索引是最好的选择。但即使有了这些索引,“高岗”也是
eu
表,它将所有这些行分块并排序

为了获得更合理的性能,您可能需要引入一个“预计算结果”表。为每件事生成计数会很昂贵。。。但是我们可以提前支付这个价格

CREATE TABLE usage_track_by_day
( updated_dt DATE NOT NULL
, PRIMARY KEY (track_id, updated_dt)
)
AS
SELECT eu.track_id
     , DATE(eu.updated) AS updated_dt
     , SUM(IF(eu.action = 1,1,0) AS cnt
  FROM entityusage eu
 WHERE eu.track_id IS NOT NULL
   AND eu.updated IS NOT NULL
 GROUP
    BY eu.track_id
     , DATE(eu.updated)
关于entityusage(跟踪id、更新、操作)的索引
可能有助于提高性能

然后,我们可以针对新的“预计算结果”表编写一个查询,从而更好地获得合理的性能

“预计算结果”表将过时,需要定期刷新


这不一定是解决该问题的最佳解决方案,但它是我们可以在数据仓库/数据集市应用程序中使用的一种技术。这让我们可以通过大量的细节行一次获取计数,然后保存这些计数以便快速访问。

你能试试这个吗。没有你提供的一些样本数据,我真的无法测试它。 在这种情况下,查询首先在表跟踪中查找,然后连接其他表

 SELECT 
    date_format(eu.updated, '%Y-%m-%d') AS p
    , count(eu.id) AS c
FROM track AS t
INNER JOIN entityusage_file AS euf ON t.id = euf.track_id
INNER JOIN entityusage AS eu ON euf.entityusage_id = eu.id
 WHERE
    t.album_id = '0054a47e-b594-407b-86df-3be078b4e7b7'
        AND entitytype = 't'
        AND ACTION = 1
GROUP BY date_format(eu.updated, '%Y%m%d');

你能试试这个吗。没有你提供的一些样本数据,我真的无法测试它。 在这种情况下,查询首先在表跟踪中查找,然后连接其他表

 SELECT 
    date_format(eu.updated, '%Y-%m-%d') AS p
    , count(eu.id) AS c
FROM track AS t
INNER JOIN entityusage_file AS euf ON t.id = euf.track_id
INNER JOIN entityusage AS eu ON euf.entityusage_id = eu.id
 WHERE
    t.album_id = '0054a47e-b594-407b-86df-3be078b4e7b7'
        AND entitytype = 't'
        AND ACTION = 1
GROUP BY date_format(eu.updated, '%Y%m%d');

假设
entityusage\u file
主要是一个多:多映射表,请参阅以获取改进它的提示。注意,它要求去掉
id
并创建一对两列索引,其中一个是
主键(track\u id,entityusage\u id)
。因为您的表有一些额外的列,所以该链接并没有涵盖所有内容

uuid可以从108字节缩减到36字节,然后通过进入
BINARY(16)
并使用压缩功能缩减到16字节。存在许多(包括版本8.0中的内置对);我的

要解释一件事。。。查询执行应该从
跟踪开始(假设
'0054a47e-b594-407b-86df-3be078b4e7b7'
是非常有选择性的)。问题是没有索引可以从那里转到下一个表。戈登建议的索引包括这样的内容

date\u格式(eu.updated,'%Y-%m-%d')
date\u格式(eu.updated,'%Y%m%d')
可以简化为
date(eu.updated)
。(无重大性能变化。)


(其他答案和评论涉及许多问题;我在这里不再重复。)

假设
entityusage\u文件
主要是一个多:多映射表,请参阅以获取改进它的提示。注意,它要求去掉
id
并创建一对两列索引,其中一个是
主键(track\u id,entityusage\u id)
。因为您的表有几个额外的列,所以t