规格:
表:
CREATE TABLE `x` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`a` INT(10) UNSIGNED NOT NULL,
`time` DECIMAL(16,6) NOT NULL,
PRIMARY KEY (`id`),
INDEX `a` (`a`),
INDEX `time` (`time`),
INDEX `time_a` (`time`, `a`)
)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=298846
;
查询:
SELECT COUNT(DISTINCT `a`) c
FROM `x`
WHERE `time` >= (UNIX_TIMESTAMP()- (60 * 24));
如果在给定范围内有很多行time
,则此查询非常缓慢。另请注意,虽然可能存在大量匹配行(数千或数万或更多),但DISTINCT
a
的数量总是相当小(几百)。
在以下情况下,查询很快(基本上是即时的),无论表的大小如何:
time
WHERE
部分(由于a
上的索引)这让我觉得在计算时它无论如何都无法使用a
上的索引,即使EXPLAIN
提到了possibly_keys
中的所有三个索引。
即使出现以下情况,问题仍然存在:
time
的类型为BIGINT
或DATETIME
(对查询进行了相应的更改)ENGINE=MyISAM
有什么建议吗?
答案 0 :(得分:1)
SELECT COUNT(DISTINCT `a`)
FROM `x`;
将跳过INDEX(a)
。请参阅EXPLAIN FORMAT=JSON SELECT ...
并查找"using_index_for_group_by": true
。当只有少量不同的a
值时,这会非常快。
我怀疑使用WHERE
子句会说"using_index_for_group_by": "scanning"
,这意味着效率较低。我怀疑实施者做的是单键案例,而不是多键案例。
这是整个表的定义吗?我看到AUTO_INCREMENT
没有任何索引。怎么了?关于与本讨论相关的MyISAM和InnoDB之间的唯一区别是PRIMARY KEY
的处理。
time
的数据类型可能不重要。
如果我对您的"任何建议不满意?"问题,请重新提问。
答案 1 :(得分:0)
尝试使用索引提示强制查询使用您希望它使用的索引。
SELECT COUNT(DISTINCT `a`) c
FROM `x` FORCE INDEX (the_index_you_want_to_use)
WHERE `time` >= (UNIX_TIMESTAMP()- (60 * 24));
答案 2 :(得分:0)
最好不要在where where子句中进行任何计算。
var unixtime = UNIX_TIMESTAMP()- (60 * 24)
SELECT COUNT(DISTINCT `a`) c
FROM `x` FORCE INDEX (the_index_you_want_to_use)
WHERE `time` >= unixtime
答案 3 :(得分:0)
如果我不得不猜测,问题就是类型。 UNIX_TIMESTAMP()
返回无符号整数。您的time
变量为decimal
。这些不是相同的东西。而且,类型不匹配会使优化器混淆。
听起来表格很大,所以更改类型是不可行的(但是,如果你可以通过选择具有正确类型的新表格的数据,你可能想要测试它。)
以下内容可能有所帮助:
WHERE `time` >= cast(UNIX_TIMESTAMP() - (60 * 24) as unsigned);
您还可以声明一个本地无符号变量,并在变量中存储“常量”,以查看是否能解决性能问题。
最后,如果未使用time, a
上的索引,请尝试查询的此变体:
SELECT COUNT(*) as c
FROM (SELECT DISTINCT a
FROM `x`
WHERE `time` >= CAST(unixtime - 24 * 60 as unsigned)
) ax
我已经看到这种重组可以提高其他数据库的性能,但不是在MySQL上。