当表包含许多记录时,MySQL 查询不使用索引

phi*_*kis 4 mysql index

我有一张桌子:

CREATE TABLE `p` (  
    `id` BIGINT(20) unsigned NOT NULL,  
    `rtime` DATETIME NOT NULL,  
    `d` INT(10) NOT NULL,  
    `n` INT(10) NOT NULL,  
    PRIMARY KEY (`rtime`,`id`,`d`) USING BTREE  
) ENGINE=MyISAM DEFAULT CHARSET=latin1;  
Run Code Online (Sandbox Code Playgroud)

我对此表运行的查询是:

SELECT id, d, SUM(n) 
FROM p 
WHERE rtime BETWEEN '2012-08-25' AND DATE(now()) 
GROUP BY id, d;
Run Code Online (Sandbox Code Playgroud)

explain在一个小表(2 条记录)上运行这个查询,它告诉我它将使用我的主键索引:

id  | select_type  | table | type   | possible_keys | key     | key_len | ref  | rows | Extra
1   | SIMPLE       | p     | range  | PRIMARY       | PRIMARY | 8       | NULL | 1    | Using where; Using temporary; Using filesort
Run Code Online (Sandbox Code Playgroud)

当我在具有 3.5 亿条记录的同一个表上运行此查询时 - 它更喜欢遍历所有记录并忽略我的键:

id  | select_type  | table  | type | possible_keys  | key  | key_len | ref  | rows      | Extra
1   | SIMPLE       | p      | ALL  | PRIMARY        | NULL | NULL    | NULL | 355465280 | Using where; Using temporary; Using filesort
Run Code Online (Sandbox Code Playgroud)

显然,这是极其缓慢的。

我试过丢失GROUP BY甚至将其更改BETWEEN为简单的“>”,但它仍然不会使用密钥。

我唯一一次让它使用密钥是在我使用'rtime = ..'.

我应该注意,所有这些数据实际上只在一周前发生,所以当我尝试获取时,rtime > 3-days-ago我期望从这 3.5 亿条记录中获得很大一部分。

这个查询应该每 15 分钟运行一次,所以目前 30-40 分钟的执行时间是绝对不能接受的。

我如何构造查询以使其使用索引,或者我应该如何索引表以获得最快的性能?

Ric*_*mes 5

桌子大小不是小人。这是估计的行数。

在这种情况下,查询优化器(MyISAM、以 rtime 开头的键等)将执行以下操作:

  1. 根据“WHERE rtime BETWEEN...”估计要扫描的表的百分比
  2. 如果这是“小”(比方说,小于 20%,但这不是一个硬数字),请使用 INDEX;否则做一个表扫描。

步骤 1 取决于与 MyISAM 表一起保存的“统计信息”。统计数据通常非常准确,但可能会变得不那么准确。ANALYZE TABLE 是解决这个问题的方法。(我认为我从未见过需要每月运行 ANALYZE;通常根本不需要它。)

to-INDEX-or-not-to-INDEX 问题的原因是这样的......使用 INDEX 时,执行必须在索引“行”和数据行之间反弹。数据行可能随机分散,导致(可能)大量 I/O。因此,在某个时间点之后最好进行“表扫描”。