为什么 MySQL 查询执行计划在添加新列后发生了变化?
Why MySQL query execution plan changed after add new columns?
我有一个 table 和下面的一些数据:
-- MySQL dump 10.13 Distrib 5.7.17, for macos10.12 (x86_64)
--
-- Host: localhost Database: testmysql
-- ------------------------------------------------------
-- Server version 5.7.17-log
--
-- Table structure for table `t_strange_index`
--
DROP TABLE IF EXISTS `t_strange_index`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `t_strange_index` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`status` tinyint(4) NOT NULL,
`create_time` bigint(20) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_time_status` (`create_time`,`status`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=21
DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPACT;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `t_strange_index`
--
LOCK TABLES `t_strange_index` WRITE;
/*!40000 ALTER TABLE `t_strange_index` DISABLE KEYS */;
INSERT INTO `t_strange_index` VALUES
(1,1,1532745820825),(2,1,1532745864183),(3,1,1532745895207),
(4,1,1532746773225),(5,1,1532746773225),(6,1,1532746773225),
(7,1,1532746822078),(8,1,1532746822078),(9,1,1532746822078),
(10,1,1532746979836),(11,1,1532746979836),(12,1,1532746979836),
(13,9,1532763766641),(14,10,1532764510924),(15,10,1532765436500),
(16,20,1532777350303),(17,9,1532777818806),(18,10,1532782628840),
(19,10,1532782711973),(20,10,1532784164740);
/*!40000 ALTER TABLE `t_strange_index` ENABLE KEYS */;
UNLOCK TABLES;
然后得到这个SQL的查询执行计划:
explain select * from t_strange_index
where create_time >= 1532746822078
and create_time <= 1532746979836
and status = 1;
结果:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
1 | SIMPLE | t_strange_index | NULL | range | idx_time_status | idx_time_status | 9 | NULL | 6 | 10.00 | Using where; Using index
但是在我添加新列后,
alter table t_strange_index add column `new column` bigint(20) NOT NULL default 123;
执行计划变更:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
1 | SIMPLE | t_strange_index | NULL | ALL | idx_time_status | NULL | NULL | NULL | 20 | 5.00 | Using where
它不再使用索引。
谁能告诉我为什么会这样?谢谢。
覆盖指数
你应该注意到你原来的 EXPLAIN extra 有 Using Index。这是覆盖指数的指标。
覆盖索引是包含查询所需的所有列的索引。
当你添加新列时,idx_time_status不再是覆盖索引(因为你选择的是*,而新列不在索引中),MySQL必须返回到原始数据。这就是为什么 MySQL 决定不使用索引更有效。
"Covering index" 只是故事的一部分。
这是倒退的:
KEY `idx_time_status` (`create_time`,`status`) USING BTREE
索引以 =
个测试开始,然后以范围结束。也就是说,这个索引将更适合您的查询:
INDEX(status, create_time)
这将不必跨行 status != 1
;索引将它们过滤掉。
但这并不能解释回避索引并切换到 table 扫描的原因。对此的解释是,范围可能超过 table 的 20%。优化器查看统计数据并讨论是使用索引更快(当只需要几行时)还是忽略索引并简单地扫描 table(当需要许多行时)。
如果您的测试用例在给定范围内有更多行,它可能有不同的 EXPLAIN
。
我有一个 table 和下面的一些数据:
-- MySQL dump 10.13 Distrib 5.7.17, for macos10.12 (x86_64)
--
-- Host: localhost Database: testmysql
-- ------------------------------------------------------
-- Server version 5.7.17-log
--
-- Table structure for table `t_strange_index`
--
DROP TABLE IF EXISTS `t_strange_index`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `t_strange_index` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`status` tinyint(4) NOT NULL,
`create_time` bigint(20) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_time_status` (`create_time`,`status`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=21
DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPACT;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `t_strange_index`
--
LOCK TABLES `t_strange_index` WRITE;
/*!40000 ALTER TABLE `t_strange_index` DISABLE KEYS */;
INSERT INTO `t_strange_index` VALUES
(1,1,1532745820825),(2,1,1532745864183),(3,1,1532745895207),
(4,1,1532746773225),(5,1,1532746773225),(6,1,1532746773225),
(7,1,1532746822078),(8,1,1532746822078),(9,1,1532746822078),
(10,1,1532746979836),(11,1,1532746979836),(12,1,1532746979836),
(13,9,1532763766641),(14,10,1532764510924),(15,10,1532765436500),
(16,20,1532777350303),(17,9,1532777818806),(18,10,1532782628840),
(19,10,1532782711973),(20,10,1532784164740);
/*!40000 ALTER TABLE `t_strange_index` ENABLE KEYS */;
UNLOCK TABLES;
然后得到这个SQL的查询执行计划:
explain select * from t_strange_index
where create_time >= 1532746822078
and create_time <= 1532746979836
and status = 1;
结果:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
1 | SIMPLE | t_strange_index | NULL | range | idx_time_status | idx_time_status | 9 | NULL | 6 | 10.00 | Using where; Using index
但是在我添加新列后,
alter table t_strange_index add column `new column` bigint(20) NOT NULL default 123;
执行计划变更:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
1 | SIMPLE | t_strange_index | NULL | ALL | idx_time_status | NULL | NULL | NULL | 20 | 5.00 | Using where
它不再使用索引。
谁能告诉我为什么会这样?谢谢。
覆盖指数
你应该注意到你原来的 EXPLAIN extra 有 Using Index。这是覆盖指数的指标。
覆盖索引是包含查询所需的所有列的索引。
当你添加新列时,idx_time_status不再是覆盖索引(因为你选择的是*,而新列不在索引中),MySQL必须返回到原始数据。这就是为什么 MySQL 决定不使用索引更有效。
"Covering index" 只是故事的一部分。
这是倒退的:
KEY `idx_time_status` (`create_time`,`status`) USING BTREE
索引以 =
个测试开始,然后以范围结束。也就是说,这个索引将更适合您的查询:
INDEX(status, create_time)
这将不必跨行 status != 1
;索引将它们过滤掉。
但这并不能解释回避索引并切换到 table 扫描的原因。对此的解释是,范围可能超过 table 的 20%。优化器查看统计数据并讨论是使用索引更快(当只需要几行时)还是忽略索引并简单地扫描 table(当需要许多行时)。
如果您的测试用例在给定范围内有更多行,它可能有不同的 EXPLAIN
。