MySQL-在字段上使用函数不会走索引的原因是什么?

题记

首先,祝大家新年快乐,牛年大吉,万事如意!

我们常常从关于MySQL的各种技术博客、书籍上面看到这样一句话:“不能在字段上使用函数,否则就不会走索引”。那么这句话的依据是什么呢?

本文就围绕“不能在字段上使用函数,否则就不会走索引”这句话来展开分析。

Talk is cheap. Show me the code

假设存在这样一张交易记录表-trade_log,该表中存在三个字段id(主键),operator_id(操作人id)、create_time(交易时间)。建表语句:

CREATE TABLE `trade_log` (
  `id` int NOT NULL AUTO_INCREMENT,
  `operator_id` int DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB ;

表中有13条记录:

INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (1, 1, '2021-02-18 09:36:22');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (2, 2, '2021-02-18 09:37:32');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (3, 3, '2021-03-04 09:37:34');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (4, 4, '2021-04-08 09:37:37');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (5, 5, '2021-05-08 09:37:40');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (6, 6, '2021-02-18 09:37:42');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (7, 7, '2021-02-18 09:37:46');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (8, 8, '2021-02-18 09:37:49');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (9, 9, '2021-02-18 09:37:53');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (10, 10, '2021-02-18 09:37:57');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (11, 11, '2021-02-18 09:37:59');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (12, 12, '2021-02-18 09:38:01');
INSERT INTO `user_database`.`trade_log`(`id`, `operator_id`, `create_time`) VALUES (13, 13, '2021-02-18 09:38:06');

现在有一个需求:统计所有二月份的交易记录总数,SQL语句是这样:

select count(*) from trade_log where MONTH(create_time) = 2;

可以看到,在create_time这个条件字段上使用了month()函数,首先我们先猜测下有没有走idx_create_time这个索引呢?

使用explain命令,查看这条SQL语句的执行结果:

mysql> EXPLAIN select count(*) from trade_log where MONTH(create_time) = 2;
+----+-------------+-----------+------------+-------+---------------+-----------------+---------+------+------+----------+--------------------------+
| id | select_type | table     | partitions | type  | possible_keys | key             | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-----------+------------+-------+---------------+-----------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | trade_log | NULL       | index | NULL          | idx_create_time | 6       | NULL |   13 |   100.00 | Using where; Using index |
+----+-------------+-----------+------------+-------+---------------+-----------------+---------+------+------+----------+--------------------------+
1 row in set (0.02 sec)

答案是不是和预想的不一样,居然走idx_create_time索引了!那么这是为什么呢?

在分析问题之前,需要先明白两个概念:索引快速定位全索引扫描

我们都知道InnoDB是索引组织表,底层数据结构为B+树,而B+树提供的索引快速定位能力是基于同一层兄弟节点有序性来实现的。
idx_create_time索引示意图
上图是idx_create_time的索引示意图,方框上的数字是month()函数计算框内日期后得到的值。正因为是B+树提供的索引快速定位能力是基于同一层兄弟节点有序性来实现的这个原因,当传入2(month(create_time) = 2)的时候,在树的第一层就不知道该怎么办了。因为只有对索引使用month()函数计算后,才知道到底等不等于2,而这就破坏了索引值的有序性,因此优化器就决定放弃走树搜索功能(索引快速定位)。

但是并不意味着优化器放弃使用这个索引,explain命令返回的Extra 字段中的Using index以及key = idx_create_time佐证了这一点。

虽然无法使用索引快速定位能力,但是优化器可以选择遍历主键索引,也可以选择遍历索引idx_create_time,但是优化器对比这两个索引大小后发现,索引idx_create_time更小,遍历这个索引树意味着比遍历主键索引树来得更快(主键索引叶子节点存放着行数据,这意味着每个数据页上能存放的索引较少),因此最终还是会选择使用索引idx_create_time。即使用全索引扫描,rows = 13证明了的确进行了全表扫描。

既然我们已经知道在字段上使用函数导致不能走索引的原因,那么我们可以在字段上进行范围查询来让优化器使用索引快速定位能力。

select count(*) from trade_log where create_time BETWEEN '2021-02-01 00:00:00' AND '2021-03-01 00:00:00'; /*如果有其它年份继续拼接**/

继续通过explain命令来查看执行结果:

mysql> EXPLAIN select count(*) from trade_log where create_time BETWEEN '2021-02-01 00:00:00' AND '2021-03-01 00:00:00';
+----+-------------+-----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
| id | select_type | table     | partitions | type  | possible_keys   | key             | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | trade_log | NULL       | range | idx_create_time | idx_create_time | 6       | NULL |   10 |   100.00 | Using where; Using index |
+----+-------------+-----------+------------+-------+-----------------+-----------------+---------+------+------+----------+--------------------------+
1 row in set (0.03 sec)

可以发现这一次的rows就等于10,意味着走了索引快速定位,而不是全索引扫描。

总结

因为在存在索引的字段上执行了函数操作(month()),而函数可能会破坏索引值的有序性(B+树提供的索引快速定位能力是基于同一层兄弟节点有序性来实现,因此导致优化器决定放弃索引快速定位能力)而转向全索引扫描。

解决上面列出的这个场景所引发的问题并不难,我们可以利用范围查询来解决。但现实中会有其它更多更杂的场景,这就要我们通过业务代码或者其它方式来避免在索引字段上进行函数操作,而是在值上进行索引操作。

### 回答1: 是的,在 MySQL 中,如果在 WHERE 子句中使用函数处理列的值,则可能会导致索引失效。这是因为当使用函数处理列的值时,MySQL 将无法使用索引进行快速查找。 例如,如果在 WHERE 子句中使用 LOWER() 函数来搜索小写字符串,则 MySQL 将无法使用索引进行查找,因为索引中的值是大写的。 因此,在使用 MySQL 时,应尽量避免在 WHERE 子句中使用函数处理列的值,以便使用索引进行优化查询。 ### 回答2: 在MySQL中,使用函数字段进行操作会导致索引失效是有可能的。这是因为索引是建立在原始字段上的,当我们在查询中对字段使用函数时,MySQL必须对每个记录进行计算,以获取函数的结果,而不是直接使用索引。这样就会导致MySQL无法有效地利用索引来加速查询的过程,从而降低了查询性能。 举个例子,如果我们有一个名为"birthday"的字段,并在该字段上建立了索引,将其定义为DATE类型。如果我们在查询中使用函数DATE()来提取birthday字段的日期,就会导致索引失效,MySQL将无法直接使用索引来优化查询,而需要对每条记录进行日期计算。 那么如何避免这种情况呢?一种方法是尽量避免对字段使用函数,如果可能的话,应该在程序中对数据进行预处理,将计算移至程序中进行,而不是在查询中使用函数。另外,可以考虑对需要用到的字段建立更为常用的索引,以提高查询性能。 总结来说,使用函数可能会导致MySQL索引失效,因为函数会对每个记录进行计算,而无法直接使用索引来加速查询。为了避免这种情况,应尽量避免使用函数,如果需要使用函数,可以考虑在程序中进行预处理,或者对需要的字段建立更合适的索引。 ### 回答3: 在MySQL中,使用函数可能会导致索引失效的情况是存在的。 当我们在查询中使用函数来对某列进行处理时,例如使用函数对列进行计算、转换、截取等操作,MySQL优化器无法利用已经创建的索引来加速查询。这是因为函数会修改列的值或者将其转化为不同的数据类型,导致索引无法正确地匹配或者无法利用索引的有序性。 例如,我们有一个名为age的列,并且对其创建了一个B-tree索引。如果我们在查询中使用函数对age列进行计算,如SELECT * FROM table WHERE SQRT(age) > 10,那么MySQL无法对SQRT(age)这个表达式进行优化,无法利用age的索引,会导致索引失效。 另外,对于字符串类型的列,使用函数进行截取或者转换操作也会导致索引失效。因为函数对字符串的处理会导致索引无法正确地进行匹配,从而无法利用索引加速查询。 为了避免这种情况,可以考虑在实际存储数据时对需要进行函数处理的列进行预处理,将处理后的结果存储在新列中,并对新列创建索引。这样在查询时就可以直接使用新列的索引,避免函数导致的索引失效。 综上所述,MySQL使用函数可能导致索引失效,这时需要注意对需要处理的列进行预处理或者重新设计查询语句,以避免影响查询性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值