我有一张这样的表,它有数百万条记录:
CREATE TABLE `myTable` (
`DateTime` DATETIME NOT NULL,
`Col1` MEDIUMINT UNSIGNED NOT NULL,
`Col2` MEDIUMINT UNSIGNED NOT NULL,
`Col3` MEDIUMINT UNSIGNED NOT NULL,
`Col4` MEDIUMINT UNSIGNED NOT NULL,
`Event` MEDIUMINT UNSIGNED NOT NULL,
`State` MEDIUMINT UNSIGNED NOT NULL,
PRIMARY KEY (`DateTime`,`Col4`,`Event`,`State`)
);
我运行一个查询来计算一个时间段的记录数,如果它们与“过滤器”相匹配,则计算一个时间段。基于Col1 / Col2 / Col3 / Col4值。例如,3分钟的时间段:
select
FROM_UNIXTIME(UNIX_TIMESTAMP(MIN(`DateTime`))-(UNIX_TIMESTAMP(MIN(`DateTime`)) % (3*60))) as 'Period',
count(*) as 'NumberOfRecords'
from
`myTable`
where
`DateTime` > '2016-09-01' and `DateTime` < '2016-09-09'
AND `Col1` IN (3, 6, 11, 14, etc... )
AND `Col2` IN (5 ,25 , 325 , 293, 294, etc.... )
AND `Col3` IN (3 , 9 , 95 , 395 , 435, etc...)
AND `Col4` IN (124, 125, 135, 325, etc...)
group by
UNIX_TIMESTAMP(`DateTime`) DIV (3*60);
我应该使用什么索引加快此查询?我不关心插入速度慢,我希望查询运行得非常快。
一般来说,每个col1,col2,col3,col4大约有1,000个唯一值,但是有数百万条记录适合日期范围。
我在想这样的事情:
CREATE INDEX `myIndex` ON `myTable` ( `DateTime`, `Col`,`Col2`,`Col3`,`Col4 )
但我不确定我的订购权是否正确?或者最好制作4个索引,每个索引一个(DateTime
,ColX
)?
答案 0 :(得分:2)
根据您的五列表结构,此查询很难优化,因为您最多可以运行六个不同的范围谓词。
范围谓词包括操作>
,<
,<>
,BETWEEN
,LIKE
或IN()
。基本上,=
以外的任何类型的搜索。
范围谓词可能与列中的许多值匹配。
等式谓词恰好匹配列中的一个值(可能有许多行具有该值,但它是一个值)。
在定义索引时,放在索引中的列应该首先是等式比较中的列引用,然后是范围谓词中引用的一个列。除了范围谓词中引用的第一列之外的索引中的任何其他列将不计入进行查找。
例如,如果您在(col1, col2, col3)
上有索引,则具有以下条件:
WHERE col1=123 AND col2 IN (4, 5, 6) AND col3=789
此查询可以使用索引的前两列。 col3
不会使用索引。查询将检查前两个术语匹配的所有行,并逐个评估所有这些行的第三个术语。
然而,相同的索引将使用所有三列来按以下条件进行查找:
WHERE col1=123 AND col2=789 AND col3 IN (4, 5, 6)
即,前两列的等式谓词和索引中最后一列的范围谓词。
使用EXPLAIN时,其中一列会报告索引条目的字节数。在上面的示例中,假设所有三列都是32位整数列。第一个查询的EXPLAIN将报告它使用8个字节(两个整数值),而第二个查询的EXPLAIN将报告它使用12个字节(三个整数值)。
在您的情况下,您的条件中的所有条款都有范围谓词。使用B-Tree索引无法优化。它可以使用任何一列的索引。因此,您可以在其中一列上创建五个单独的索引,并希望优化器选择最有效缩小搜索范围的索引。或者您可以使用index hints自行选择最佳索引。
我写了一篇你可能会感兴趣的演讲,名为How to Design Indexes, Really 。这是我演示文稿的录音:https://www.youtube.com/watch?v=ELR7-RdU9XU
除B树索引外还有其他类型的索引。对多列进行范围谓词搜索可能需要R树索引。因此,您可能会发现要真正优化此查询,您需要将数据的副本加载到Apache Solr或Crate或其他类似的搜索引擎。
答案 1 :(得分:1)
您建议的索引可能是您可以做的最好的索引。所有比较都是不等式,因此只能使用索引中的第一个键来满足where
子句。并且,日期可能是最有选择性(或一致选择性)的列。
包括其他列很有帮助。
至于排序IN
列表,这是不必要的:MySQL会为你做。更好的是,MySQL创建了一个二叉树结构,因此搜索效率更高。 (请注意,在其他数据库中,您希望按频率而非值对值进行排序,因为列表将按顺序处理。)
遗憾的是,无法使用聚合索引。因此,除非您能够弄清楚如何将条件更改为AND
所连接的严格相等条件,否则您可能会遇到查询的性能问题。
答案 2 :(得分:1)
可能有更好的方法。
你显然每个组合都有很多行,因为你提取COUNT(*)
,对吗?
你只需要3分钟的间隔吗?让我们说“是”&#39;。然后,让我们构建这个&#34;摘要表&#34;:
CREATE TABLE Summary1234 (
`Period` DATETIME NOT NULL,
`Col1` MEDIUMINT UNSIGNED NOT NULL,
`Col2` MEDIUMINT UNSIGNED NOT NULL,
`Col3` MEDIUMINT UNSIGNED NOT NULL,
`Col4` MEDIUMINT UNSIGNED NOT NULL,
NumberOfRecords SMALLINT UNSIGNED NOT NULL,
PRIMARY KEY (`DateTime`,col1, col2, col3, col4)
);
然后有效(并逐步)做
INSERT INTO Summary 1234
SELECT FROM_UNIXTIME(UNIX_TIMESTAMP(MIN(`DateTime`)) -
(UNIX_TIMESTAMP(MIN(`DateTime`)) % (3*60))) as 'Period',
col1, col2, col3, col4,
count(*) as 'NumberOfRecords'
FROM mytable
GROUP BY 1,2,3,4,5;
我说&#34;渐进地&#34;因为你应该每隔3分钟用所有新行更新这个表。
然后从此表中进行查询:
select Period,
SUM(NumberOfRecords) AS NumberOfRecords
from `myTable`
where `Period` >= '2016-09-01' -- note: I fixed your inequality
and `Period` < '2016-09-09'
AND `Col1` IN (3, 6, 11, 14, etc... )
AND `Col2` IN (5 ,25 , 325 , 293, 294, etc.... )
AND `Col3` IN (3 , 9 , 95 , 395 , 435, etc...)
AND `Col4` IN (124, 125, 135, 325, etc...)
GROUP BY Period;
如果计数通常为10,则此摘要将明显更好。 如果它们很少超过1,这将不是非常有用。 (因此,我说&#34;可能会更好&#34;。)
如果您需要6分钟的间隔或1小时的间隔等,您可以执行略微不同的SELECT
,以便从一个摘要表中实现此目的。不要只为这种差异制作多个汇总表。