我有一个查询,其目的是生成有关在不同时段(按月,按季度,按年等)从网站下载了多少音乐作品(曲目)的统计数据。该查询对表entityusage
,entityusage_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_file
会保留所有曲目这张专辑"翻译成了#34;到了下载点。)
此查询需要40 - 50秒。我一直试图优化这个查询一段时间,但我觉得我接近这个错误的方式。
这是必须运行以生成报告的4个类似查询中的一个。报告最好能够在用户等待时完成。现在,我看了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;
查询中的EXPLAIN
为我提供了以下内容:
+------+-------------+-------+--------+----------------+----------------+---------+------------------------------+---------+----------------------------------------------+
| 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 |
+------+-------------+-------+--------+----------------+----------------+---------+------------------------------+---------+----------------------------------------------+
答案 0 :(得分:2)
这是您的查询:
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_id, id)
,entityusage_file(track_id, entityusage_id)
和entityusage(id, entitytype, action)
上建立索引。
答案 1 :(得分:2)
假设entityusage_file
主要是多个:多个映射表,请参阅this以获取有关改进它的提示。请注意,它要求删除id
并创建一对2列索引,其中一个是PRIMARY KEY(track_id, entityusage_id)
。由于您的表格中有一些额外的列,因此该链接并不涵盖所有内容。
UUID可以从108字节缩减到36,然后通过转到BINARY(16)
并使用压缩函数缩小到16。存在许多(包括版本8.0中的内置对); here's我的。{/ p>
解释一件事......查询执行应该以{{1}}开始(假设track
非常有选择性)。挂断的是没有索引从那里到下一个表。戈登的建议索引包括这样的内容。
'0054a47e-b594-407b-86df-3be078b4e7b7'
和date_format(eu.updated, '%Y-%m-%d')
可以简化为date_format(eu.updated, '%Y%m%d')
。 (没有显着的性能变化。)
(其他答案和评论涵盖了一些问题;我在此不再重复。)
答案 2 :(得分:1)
因为GROUP BY操作是在涉及函数的表达式上,所以MySQL不能使用索引来优化该操作。它需要“使用filesort”操作。
鉴于目前的表格定义,我认为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)
索引ON entityusage (track_id,updated,action)
可能会使效果受益。
然后,我们可以针对新的“预先计算结果”表编写一个查询,并以合理的性能更好地拍摄。
“预先计算的结果”表格会过时,需要定期刷新。
这不一定是该问题的最佳解决方案,但它是我们可以在数据仓库/数据智能应用程序中使用的技术。这让我们可以通过大量细节行来计算一次,然后保存这些计数以便快速访问。
答案 3 :(得分:1)
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');