这周收到一个 sentry 报警,如下 SQL 查询超时了。
select * from order_info where uid = 5837661 order by id asc limit 1
执行show create table order_info
发现这个表其实是有加索引的。
CREATE TABLE `order_info` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(11) unsigned,
`order_status` tinyint(3) DEFAULT NULL,
... 省略其它字段和索引
PRIMARY KEY (`id`),
KEY `idx_uid_stat` (`uid`,`order_status`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8
理论上执行上述 SQL 会命中 idx_uid_stat 这个索引,但实际执行 explain 查看:
explain select * from order_info where uid = 5837661 order by id asc limit 1
可以看到它的 possible_keys(此 SQL 可能涉及到的索引) 是 idx_uid_stat,但实际上(key)用的却是全表扫描。
我们知道 MySQL 是基于成本来选择是基于全表扫描还是选择某个索引来执行最终的执行计划的,所以看起来是全表扫描的成本小于基于 idx_uid_stat 索引执行的成本,不过我的第一感觉很奇怪,这条 SQL 虽然是回表,但它的 limit 是 1,也就是说只选择了满足 uid = 5837661 中的其中一条语句,就算回表也只回一条记录,这种成本几乎可以忽略不计,优化器怎么会选择全表扫描呢。
为了查看 MySQL 优化器为啥选择了全表扫描,我打开了 optimizer_