MySql组合索引最左侧原则失效

最近在看MySQL索引的知识,看到组合索引的时候,有一个最左侧原则,我就建了个简单的表验证一下,过程中碰到有些不懂的,在这里求教一下,mysql版本5.7的

CREATE TABLE `testp` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `users_id` int(10) unsigned NOT NULL COMMENT '学生用户ID',
  `activity_id` int(11) NOT NULL COMMENT '活动ID',
  `phone` varchar(20) NOT NULL COMMENT '手机号码',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_activity_users` (`activity_id`,`users_id`) USING BTREE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='名表';

-- ----------------------------
-- Records of testp
-- ----------------------------
INSERT INTO `testp` VALUES ('1', '272002', '17', '12345678900');
INSERT INTO `testp` VALUES ('2', '290253', '19', '12345678900');
INSERT INTO `testp` VALUES ('3', '177652', '17', '12345678900');
INSERT INTO `testp` VALUES ('4', '36482', '17', '12345678900');
INSERT INTO `testp` VALUES ('5', '36482', '19', '12345678900');
INSERT INTO `testp` VALUES ('6', '290253', '17', '12345678900');
INSERT INTO `testp` VALUES ('7', '272207', '17', '12345678900');
INSERT INTO `testp` VALUES ('8', '272207', '19', '12345678900');

表结构如上,
activity_id,users_id组合一下,navicat上信息如下图:
图片描述

首先运行第一条sql:

EXPLAIN SELECT * FROM `testp` WHERE activity_id=17  AND users_id=272002;

其结果如下图:
图片描述

发现key这个列有值,这个是对的

然后我运行如下sql:

EXPLAIN SELECT * FROM `testp` WHERE activity_id=17;

结果是:
图片描述

发现没有使用到索引,正常情况不是可以使用到索引吗?

之后我又去试了把 activity_id=17改成 activity_id=19:

EXPLAIN SELECT * FROM `testp` WHERE activity_id=19;

结果:
图片描述

结果显示使用到了索引

也就是说这个和值也有关系???请各位解答下,谢谢!!

之前以为是Unique类型的原因,我把Unique改成Normal结果还是一样的。

阅读 8k
3 个回答

mysql优化器认为全表扫描的成本小于使用索引,所以放弃使用索引了。

如果是

EXPLAIN SELECT activity_id FROM `testp` WHERE activity_id=17 ;

的话应该是总是会使用索引的,因为不需要回表


举一个强行走索引可能比直接全表扫描慢的极端例子

对问题中的那张表,假如表中数据如下

INSERT INTO `testp` VALUES ('1', '272002', '19', '12345678900');
INSERT INTO `testp` VALUES ('2', '290253', '19', '12345678900');
INSERT INTO `testp` VALUES ('3', '177652', '19', '12345678900');
INSERT INTO `testp` VALUES ('4', '36482', '19', '12345678900');
INSERT INTO `testp` VALUES ('5', '36483', '19', '12345678900');
INSERT INTO `testp` VALUES ('6', '290254', '19', '12345678900');
INSERT INTO `testp` VALUES ('7', '272207', '19', '12345678900');
INSERT INTO `testp` VALUES ('8', '2722067', '19', '12345678900');
INSERT INTO `testp` VALUES ('9', '272209', '19', '12345678900');
INSERT INTO `testp` VALUES ('10', '272210', '19', '12345678900');

对于查询

SELECT * FROM `testp` WHERE activity_id=19

由于要查询的列包含索引中没有的列 phone,所以如果走索引idx_activity_users是需要回表的,即现通过索引获取所有activity_id为19的列的_rowid,然后根据这些_rowid到表中获取所有数据,然而由于索引中activity_id全部是19,没有筛选掉任何数据,相关的开销完全是白费的,还不如直接全表扫描。

即使不全是19,只要没能够筛掉足够多的数据,索引再回表的开销还是有可能大于直接扫表的,于是就有了possible_keys有值却没有走索引的情况。

而对于查询

SELECT id, users_id, activity_id FROM `testp` WHERE activity_id=19;

由于索引覆盖了所有查询列(INNODB中所有非主键索引都包含主键列),不需要回表,因此是会使用索引的

走索引的原则是,查询结果要小于总数的20~25%具体多少忘了,你看看id=17都超50%了,怎么可能走索引

Oracle官方说明,走不走索引要看需要查询的数据是否少于全表的30%,很明显activity_id=17的行数已经超过总行数的30%了。
其次,索引的使用涉及优化器的判断,你要明白Mysql优化器是基于成本的,也就是哪个成本低,就走什么执行计划。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题