索引是一种利用某种规则的数据结构与实际数据的关系加快数据查找的功能。我们的数据库中存储有大量的内容,而索引能够通过数据节点,根据特定的规则和算法快速查找到节点对应的实际文件的位置。简单来说索引就像书的目录,能够帮助我们准确定位到书籍具体的内容。

最近在学习索引的时候遇到了一个问题,下面我们通过重现的方式来看一下。

首先建立一个如下测试表:

CREATE TABLE `simple_table` (
  `id` int NOT NULL AUTO_INCREMENT,
  `c1` datetime DEFAULT NULL,
  `c2` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c2__idx` (`c2`),
  KEY `fun_c1_idx` ((cast(`c1` as date)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

fun_c1_idx: 是mysql8开始支持的函数索引

然后往这个表里随机插入1000 条数据。

    select * from simple_table where date(c2) = '2022-01-01';

    可以看到上面的这条 SQL 语句不能走索引。因为索引树中存储的是列的实际值和主键值,所以对条件字段做函数操作是会让索引失效的。简单来说就是,如果拿 ‘2022-01-01’ 去匹配,将无法定位到索引树中的值。因此正确选择是放弃走索引,选择全表扫描。

    我们再看下一条 SQL。

      select id,c2 from simple_table where date(c2) = '2022-01-01';

      与第一条不同,这条 SQL 只返回了部分列,而且这些列都在索引中了。然后我们用 explain 分析一下这条 SQL 的执行计划,判断它能否走索引:

      1.png

      上图可以明显看到 key 值为`c2__idx`,即走了索引。

      这里就很奇怪,不是说对条件字段做函数操作是会让索引失效吗,为什么这里又走了索引?

      这就是我当时在学习时遇到的问题,后来我发现是因为我没有搞清楚“走索引”的意思。大家都知道索引能加快查询,但是索引能加快查询的原因你知道么?答案是减少了查询的次数。

      现在我们回到上面的 SQL,可以看到虽然key 值为`c2__idx`,但是 rows 值为 1000。也就是扫描了扫描全表,即 `c2__idx`的所有记录。但是由于`c2__idx`已经包含了所有需要查询的列,优化器才选择了走这个索引。

      最后再来思考一个问题,使用了索引是否一定快?这个问题我们通过一个具体例子看一下:

        select * from simple_table;
        select * from simple_table where id > 0;

        不需要 explain 分析直接肉眼观察就能看到第一条 SQL 没有走索引,第二条 SQL 使用了主键索引。可以看到没有使用索引的速度快一些,这是因为虽然使用了索引,但是还是从主键索引的最左边的叶节点开始向右扫描整个索引树,进行了全表扫描,这让索引失去了意义。

        总结一下:查询是否使用索引,只是表示一个 SQL 语句的执行过程;而是否为慢查询,是由它执行的时间决定的,也就是说是否使用了索引和是否是慢查询两者之间没有必然的联系。我们在使用索引时,不应只关注是否起作用,而应该关心索引是否减少了查询扫描的数据行数,扫描行数减少效率才会得到提升。对于一个大表,不止要创建索引,还要考虑索引过滤性,过滤性好,执行速度才会快。