网上很多人说mysql一旦使用函数就不走函数,但是事实真的是如此吗?我先说明,并不是如此的,本篇文章会通过DAYOFWEEK()
和substr()
两个函数作为条件查询,看看究竟是否会走索引(其他函数同理),使用函数不走索引的时候又应该如何做sql优化,本篇文章重点是基于这两点进行分析。
测试数据如下:
create_time和name列是都建立了索引的。
DROP TABLE IF EXISTS `demo`;
CREATE TABLE `demo` (
`id` int NOT NULL AUTO_INCREMENT,
`create_time` datetime NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `create_time`(`create_time`) USING BTREE,
INDEX `name`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
INSERT INTO `demo` VALUES (1, '2023-04-28 10:41:16', 'zhangsan');
INSERT INTO `demo` VALUES (2, '2023-04-01 10:41:22', 'lisi');
DAYOFWEEK()
:函数返回日期的工作日索引值,即星期日为1,星期一为2,星期六为7。 这些索引值对应于ODBC标准。
通过下面会发现一个问题,假如是select *
的情况下是不会走索引的,假如是只返回使用函数的列是会走索引的。
EXPLAIN SELECT * from demo WHERE dayofweek(create_time) = 6 \G;
EXPLAIN SELECT dayofweek(create_time),create_time from demo WHERE dayofweek(create_time) = 6 \G;
关于执行计划的解读:
截取字符串语法:substr(obj,start,length)
参数:
通过下面案例会发现,跟上面的案例是一样的,同样是select *
的情况下是不会走索引的。
EXPLAIN SELECT * from demo WHERE substr(name,1,3) = 'lis'\G;
EXPLAIN SELECT substr(name,1,3),name,id from demo WHERE substr(name,1,3) = 'lis'\G;
那么问题来了遇到这种查询所有数据使用函数不走索引的我们应该如何优化。通过以下试验发现可以携带id,id是主键的情况下不会导致索引失效!
EXPLAIN SELECT substr(name,1,3),name,id,create_time from demo WHERE substr(name,1,3) = 'lis'\G;
EXPLAIN SELECT substr(name,1,3),name,id from demo WHERE substr(name,1,3) = 'lis'\G;
通过以下试验得出结论,假如使用函数作为条件查询,只能返回条件的那一列跟id主键列,一旦返回其他的列就会索引失效!
由此优化方案便出来了,假设我们要查name列当中前三个字母是lis的全行数据,然后我们想让他使用到索引,可以使用嵌套查询的方案:
这里进行提示以下:MySQL的
IN
运算符可以使用索引,但是,有一点需要注意。如果你的IN子句中包含的值很多,那么MySQL可能会选择不使用索引,因为扫描大量的值可能比使用索引更快。这个阈值通常是1000个值,但这个值是可配置的。表内数据太少使用IN
也不会使用索引!
EXPLAIN SELECT * FROM demo WHERE id in (SELECT id from demo WHERE substr(name,1,3) = 'lis') \G;
如下案例显示实际上并未使用到索引
上面测试的表当中就两条数据所以显示的in并没有使用索引,如下表内共有一万条数据,然后对主键使用in查询,可以很明显的看到,是使用了索引的。由此可证明in是会使用索引的,只不过mysql会根据权衡利弊到底使用索引快还是不使用索引快。
Mysql 5.7 中推出了一个非常实用的功能 虚拟列 Generated (Virtual) Columns
虚拟索引
”。UNIQUE
。INSERT和UPDATE
操作期间在辅助索引(辅助又叫二级索引)记录中实现虚拟列值时执行计算,因此需要考虑额外的写成本。即使有额外的写成本,虚拟列上的二级索引也可能比生成的存储列更可取,生成的存储列在集群索引中具体化,从而导致需要更多磁盘空间和内存的更大的表。如果没有在虚拟列上定义二级索引,则会产生额外的读取成本,因为每次检查列的行时都必须计算虚拟列值。语法:ALTER TABLE 表名称 add column 虚拟列名称 虚拟列类型 GENERATED ALWAYS as (表达式) [VIRTUAL | STORED];
MySQL 在处理 虚拟列存储问题的时候有两种方式:
下面我们基于substr(name,1,3)
函数来创建一个虚拟列:
ALTER TABLE demo add column virtual_name VARCHAR(5) GENERATED ALWAYS as (substr(name,1,3)) VIRTUAL;
对虚拟列添加索引:
ALTER TABLE `demo`.`demo`
ADD INDEX `virtual_name`(`virtual_name`) USING BTREE;
这时候就可以直接通过虚拟列来完成查询操作了
EXPLAIN SELECT * from demo WHERE virtual_name = 'lis';
假如使用函数作为条件查询,只能返回条件的那一列跟id主键列,一旦返回其他的列就会索引失效!针对于使用函数索引失效问题,可以使用嵌套查询来解决,也可以使用虚拟列来解决!