我们有一个数据库,其中的数据表定义如下:
CREATE TABLE data
(
msts BIGINT,
variable_id INT,
p_id INT,
value DOUBLE,
PRIMARY KEY(msts,variable_id,p_id)
);
ALTER TABLE data
ADD FOREIGN KEY (p_id)
REFERENCES p(id);
ALTER TABLE data
ADD FOREIGN KEY (variable_id)
REFERENCES variables(id);
此表可以包含数十亿条记录。
进行简单查询时:
SELECT COUNT(msts) from data FORCE INDEX(PRIMARY) where (
msts<1535886000000000000 AND msts>1535796060000000000 AND
variable_id=107 AND p_id=661 );
屈服:
+-------------+
| COUNT(msts) |
+-------------+
| 89873 |
+-------------+
1 row in set (42.51 sec)
需要42.51秒才能计算出89873。
为什么要花这么长时间才能将主键用作复合索引?
这是解释:
EXPLAIN SELECT COUNT(msts) from data FORCE INDEX(PRIMARY) where
( msts<1535886000000000000 AND msts>1535796060000000000 AND
variable_id=107 AND plant_id=661 );
给出:
rows = 190996998
filtered=0
ref=NULL
type=range
任何帮助将不胜感激!
答案 0 :(得分:2)
您的查询被重写以更改where子句的顺序。
SELECT COUNT(msts)
from data
where variable_id=107
and p_id=661
and msts>1535796060000000000
and msts<1535886000000000000;
它包含两个相等匹配项,分别在variable_id和p_id上。然后,它在msts上包含范围过滤器。因此,您需要在(variable_id, p_id, msts)
列上按该顺序 的索引,以帮助快速满足您的查询。
为什么?您可以认为MySQL索引是按顺序排序的。为了满足您的查询,MySQL随机访问了第一个合格项目的索引。然后依次扫描直到最后一项。这就是所谓的索引范围扫描。
您先前存在的索引将首先列出msts。这意味着您的索引无法顺序扫描,因为索引中的每个msts值可能在其他两列中都有很多值。
专业提示1:尽可能使用COUNT(*)
代替COUNT(column)
。第二个比较慢,因为它必须忽略任何为NULL的列值。第一个只是全部计算在内。
专业提示2:额外的单列索引只有在它们有助于加快特定查询的速度时才有用。
专业提示3 :强制使用索引几乎总是一个错误的选择。
专业建议4:阅读https://use-the-index-luke.com/
修改:您询问了如何进行转换。
如果您的表尚未包含数百万行,则只需更改主键定义即可。
ALTER TABLE data
DROP PRIMARY KEY,
ADD PRIMARY KEY (variable_id, p_id, msts);
如果它确实已经包含数十亿行,则您可能应该创建一个具有正确定义的新表,将现有表复制到该表中。然后复制您的数据。然后将旧表重命名为data_old
或其他名称,并将新表重命名为data
。这可能是一项涉及大量数据的复杂任务。如果您不明白,问另一个问题。