MySQL索引失效与MySQL8新添加的索引特性

目录

MySQL索引失效的场景

数据表和索引的准备

1 .联合索引不满足最左匹配原则

2.对索引进行表达式计算

3.对索引使用函数

4.对索引隐式类型转换

5.like的不当使用

6.where子句中的 or

7.not int导致索引部分失效

MySQL8新增的索引特性

函数索引

索引跳跃扫描(Index Skip Scan)

 倒序索引

隐藏索引

MySQL索引失效的场景

数据表和索引的准备

创建数据表和索引

CREATE TABLE `user` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',`idcard` varchar(18) DEFAULT NULL COMMENT '身份编号',`username` varchar(32) DEFAULT NULL COMMENT '用户名',`age` int(11) DEFAULT NULL COMMENT '年龄',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`id`),KEY `idx_idcard_username_age` (`idcard`,`username`,`age`),KEY `idx_create_time` (`create_time`)
);

 用存储过程来插入数据的,这里我贴出来方便你复现:

delimiter ;;
CREATE PROCEDURE insert_user(IN limit_num int)
begin
DECLARE i INT DEFAULT 1;DECLARE idcard varchar(18) ;DECLARE username varchar(32) ;DECLARE age TINYINT DEFAULT 1;WHILE i < limit_num DOSET idcard = CONCAT("NO", i);SET username = CONCAT("Tom",i);SET age = FLOOR(10 + RAND()*2);INSERT INTO `user` VALUES (NULL, idcard, username, age, NOW());SET i = i + 1;END WHILE;
end;;
delimiter ;
call insert_user(10000);

mysql版本

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.36    |
+-----------+
1 row in set (0.00 sec)

1 .联合索引不满足最左匹配原则

创建联合索引时,我们需要注意创建时的顺序问题,因为联合索引 (a, b, c) 和 (c, b, a) 在使用的时候会存在差别。

联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配。

比如,创建一个 (a, b, c) 联合索引,若查询条件是以下这几种,就可以匹配上联合索引:

  • where a=1;
  • where a=1 and b=2 and c=3;
  • where a=1 and b=2;
  • where b=1 and a=2 and c=3;

注意:因为有查询优化器,所以 a 字段在 where 子句的顺序并不重要。

但是,因为不符合最左匹配原则,所以就无法匹配上联合索引,联合索引就会失效,如下:

  • where b=2;
  • where c=3;
  • where b=2 and c=3;

那么还有另一种情况 where a=1 and c=3。这种是 联合索引中间的字段去掉了。这种情况在不同mysql版本有不同的表现。

而 MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。 

 代码测试:

--使用了联合索引idx_idcard_username_age
mysql> explain select * from user where username= 'sdf' and idcard='123' and age=32;
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys           | key                     | key_len | ref               | rows | filtered | Extra |
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_idcard_username_age | idx_idcard_username_age | 211     | const,const,const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)--不符合最右匹配原则
mysql> explain select * from user where age=32;                                     
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9800 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)--不符合最右匹配原则,但查找的是联合索引中的字段,使用了联合索引,但是查询的行数基本等于总行数
mysql> explain select username from user where username= 'sd' and age=32; 
+----+-------------+-------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys           | key                     | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | user  | NULL       | index | idx_idcard_username_age | idx_idcard_username_age | 211     | NULL | 9800 |     1.00 | Using where; Using index |
+----+-------------+-------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)--使用了索引,索引下推功能(Using index condition)优化
mysql> explain select * from user where idcard='2323' and age=32;                                              
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys           | key                     | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_idcard_username_age | idx_idcard_username_age | 75      | const |    1 |    10.00 | Using index condition |
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

这里讲解一下key_len的计算:

  • idcard 类型为varchar(18),字符集为utf8mb4,也就是使用4个字节来表示一个完整的UTF-8。此时,key_len = 18* 4 = 72;
  • 由于该字段类型varchar为变长数据类型,需要再额外添加2个字节。此时,key_len = 72 + 2 = 74;
  • 由于该字段运行为NULL(default NULL),需要再添加1个字节。此时,key_len = 74 + 1 = 75;

所以在联合索引中只使用到idcard字段时候,其key_len就是75,其他的依次类推。

还有MySQL8中新增了Index Skip Scan,这个可能就在某些情况和最左匹配原则会有冲突,后面会讲解该情况。

2.对索引进行表达式计算

看例子:

mysql> explain select * from user where id=10;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | user  | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)mysql> explain select * from user where id -1=10;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9800 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

因为索引保存的是索引字段的原始值,而不是 id + 1 表达式计算后的值,所以无法走索引,只能通过把索引字段的取值都取出来,然后依次进行表达式的计算来进行条件判断,因此采用的就是全表扫描的方式。

针对这种情况,其实不单单是索引的问题,还会增加数据库的计算负担。就以上述SQL语句为例,数据库需要全表扫描出所有的id字段值,然后对其计算,计算之后再与参数值进行比较。如果每次执行都经历上述步骤,性能损耗可能会比较大。

建议在查询条件中不要对索引进行表达式计算。

3.对索引使用函数

看例子

mysql> explain select * from user where substr(idcard,1,4) = '1100';  
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9800 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)mysql> explain select * from user where idcard = '1100';
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys           | key                     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_idcard_username_age | idx_idcard_username_age | 75      | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

和对索引进行表达式计算一样因为索引保存的是索引字段的原始值,而不是经过函数计算后的值,自然就没办法走索引了。

但是在MySQL8.0 中推出了函数索引的特性。后序会讲解。

4.对索引隐式类型转换

例子1:

idcard是varchar字符串类型。条件查询中,输入的参数是整型,会进行隐式类型转换,不会使用索引。

mysql> explain select * from user where idcard = 1100;       
+----+-------------+-------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys           | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | idx_idcard_username_age | NULL | NULL    | NULL | 9800 |    10.00 | Using where |
+----+-------------+-------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)mysql> explain select * from user where idcard = '1100';
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys           | key                     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_idcard_username_age | idx_idcard_username_age | 75      | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

例子2:另外一种情况,id是int整数类型,而条件查询中,输入的参数是字符串。这里却是使用了索引。

mysql> explain select * from user where id ='1';       
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | user  | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)mysql> select '10'>9;
+--------+
| '10'>9 |
+--------+
|      1 |
+--------+

MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较

这是从上面select '10'>9;的结果1得出的。(《mysql45讲》中提供的测试方法)

  • 如果规则是 MySQL 会自动将「字符串」转换成「数字」,就相当于 select 10 > 9,这个就是数字比较,所以结果应该是 1;
  • 如果规则是 MySQL 会将自动「数字」转换成「字符串」,就相当于 select "10" > "9",这个是字符串比较,字符串比较大小是逐位从高位到低位逐个比较(按ascii码) ,那么"10"字符串相当于 “1”和“0”字符的组合,所以先是拿 “1” 字符和 “9” 字符比较,因为 “1” 字符比 “9” 字符小,所以结果应该是 0。

那么select * from user where id ='1';  语句就相当于

select * from user where id = CAST("1" AS signed int);

 这不是在索引中使用函数,所以还是可以使用索引的。

而explain select * from user where idcard = 1100; 就相当于

explain select * from user where  CAST(idcard AS int) = 1100;

这是在索引字段使用了函数,所以最终就不会走索引。

5.like的不当使用

看例子:

mysql> explain select * from user where idcard like '%32';    
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9800 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)mysql> explain select * from user where idcard like '32%';
+----+-------------+-------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys           | key                     | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | user  | NULL       | range | idx_idcard_username_age | idx_idcard_username_age | 75      | NULL |    1 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)mysql> explain select * from user where idcard like '%32%';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9800 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

模糊查询时(like语句),模糊匹配的占位符位于条件的首部会导致索引失效。

索引本身就相当于目录,从左到右逐个排序。而条件的左侧使用了占位符,导致无法按照正常的目录进行匹配,导致索引失效就很正常了。

6.where子句中的 or

看例子:

mysql> explain select * from user where id=21 or age=23;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | PRIMARY       | NULL | NULL    | NULL | 9800 |    10.01 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)mysql> explain select * from user where age=32 or id=323;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | PRIMARY       | NULL | NULL    | NULL | 9800 |    10.01 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)--or前后字段都建立索引,使用>,<也是使用了索引的
mysql> explain select * from user where id=21 or idcard='323';
+----+-------------+-------+------------+-------------+---------------------------------+---------------------------------+---------+------+------+----------+----------------------------------------------------------------+
| id | select_type | table | partitions | type        | possible_keys                   | key                             | key_len | ref  | rows | filtered | Extra                                                          |
+----+-------------+-------+------------+-------------+---------------------------------+---------------------------------+---------+------+------+----------+----------------------------------------------------------------+
|  1 | SIMPLE      | user  | NULL       | index_merge | PRIMARY,idx_idcard_username_age | idx_idcard_username_age,PRIMARY | 75,4    | NULL |    2 |   100.00 | Using sort_union(idx_idcard_username_age,PRIMARY); Using where |
+----+-------------+-------+------------+-------------+---------------------------------+---------------------------------+---------+------+------+----------+----------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)mysql> explain select idcard from user where id<12 or id>100; 
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 4911 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

or前后的两个字段都需要有索引才不会失效。

可以这样想,若单独使用age字段作为条件很显然是全表扫描。既然已经进行了全表扫描了,前面id的条件再走一次索引反而是浪费了。所以,在使用or关键字时,切记两个条件都要添加索引,否则会导致索引失效

7.not int导致索引部分失效

看例子:

mysql> explain select * from user where id not in( 32, 11,44);    
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 4941 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)mysql> explain select * from user where idcard not in( '322', '1002');
+----+-------------+-------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys           | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | idx_idcard_username_age | NULL | NULL    | NULL | 9800 |    50.02 | Using where |
+----+-------------+-------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)mysql> explain select * from user where id in( 22, 12);   
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    2 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)mysql> explain select * from user where idcard in( '322', '1002');    
+----+-------------+-------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys           | key                     | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | user  | NULL       | range | idx_idcard_username_age | idx_idcard_username_age | 75      | NULL |    2 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

查询条件使用not in时,如果是主键则走索引,如果是普通索引,则索引失效

MySQL8新增的索引特性

函数索引

这里说的函数索引不是说对某列做了索引,之后就可以对该列进行函数操作。而是可以对该列操作函数后,把这个当做索引,之后就可以对该列使用函数

在MySQL8.0之前对条件字段做函数操作、或者做运算都将不会使用字段上的索引。

mysql> show index from user;
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name                | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| user  |          0 | PRIMARY                 |            1 | id          | A         |        9800 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| user  |          1 | idx_idcard_username_age |            1 | idcard      | A         |        9800 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| user  |          1 | idx_idcard_username_age |            2 | username    | A         |        9800 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| user  |          1 | idx_idcard_username_age |            3 | age         | A         |        9800 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| user  |          1 | idx_create_time         |            1 | create_time | A         |          74 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| user  |          1 | idx_age                 |            1 | age         | A         |           2 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
6 rows in set (0.00 sec)mysql> explain select * from user where month(create_time)=9;;            
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 9800 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)ERROR: 
No query specified

可以看到执行计划的type是ALL,并没有使用索引。在MySQL8.0中推出了函数索引的特性,其是通过虚拟列来实现的,接着就来看看通过函数索引实现相同的需求。

注意看索引名idx_create_time_month对应的列名是null,所以是虚拟列。

--要用()把month函数括起来,不然就报错
mysql> create  index idx_create_time_month on user((month(create_time)));
Query OK, 0 rows affected (0.14 sec)
Records: 0  Duplicates: 0  Warnings: 0mysql> show index from user;
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+----------------------+
| Table | Non_unique | Key_name                | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression           |
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+----------------------+
| user  |          0 | PRIMARY                 |            1 | id          | A         |        9800 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL                 |
| user  |          1 | idx_idcard_username_age |            1 | idcard      | A         |        9800 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL                 |
| user  |          1 | idx_idcard_username_age |            2 | username    | A         |        9800 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL                 |
| user  |          1 | idx_idcard_username_age |            3 | age         | A         |        9800 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL                 |
| user  |          1 | idx_create_time         |            1 | create_time | A         |          74 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL                 |
| user  |          1 | idx_create_time_month   |            1 | NULL        | A         |           1 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | month(`create_time`) |
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+----------------------+
6 rows in set (0.00 sec)mysql> explain select * from user where month(create_time)=9;
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys         | key                   | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_create_time_month | idx_create_time_month | 5       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

创建函数索引后,这里就是使用索引的了。

注意:在使用函数索引时必须依照定义时的语法进行使用,否则优化器无法识别。

索引跳跃扫描(Index Skip Scan)

MySQL 8.0.13 版本对于range查询,引入了索引跳跃扫描(Index Skip Scan)优化,支持不符合组合索引最左前缀原则条件下的SQL,依然能够使用组合索引,减少不必要的扫描。

CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL, PRIMARY KEY(f1, f2));
INSERT INTO t1 VALUES(1,1), (1,2), (1,3), (1,4), (1,5),(2,1), (2,2), (2,3), (2,4), (2,5);
INSERT INTO t1 SELECT f1, f2 + 5 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 10 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 20 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 40 FROM t1;
ANALYZE TABLE t1;
EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 > 40;执行ANALYZE TABLE,MySQL会分析指定表的键的值(主键、唯一键、外键等,也可以看成就是索引列的值)分布情况,并会记录分布情况。
不执行的话,mysql可能不会使用index for skip scan。应该执行ANALYZE TABLE,就记录了数据分布情况。结果:
mysql> EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 = 40;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra                                  |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | range | PRIMARY       | PRIMARY | 8       | NULL |   16 |   100.00 | Using where; Using index for skip scan |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------+
1 row in set, 1 warning (0.00 sec)

explain返回的Extra中就有Using index for skip scan。

这种优化一般是适用于左边字段唯一性较差的情况,例如性别之类的值,否则优化器则不会使用Index Skip Scan来进行优化。

跳跃范围扫描使用也是有一些限制:

  • 表上至少存在一个联合索引([A_1,A_2...A_k],B_1,B_2...B_m,C,[,D_1,...,D_n]),其中A部分以及D部分可以为空,但是B和C部分不能为空。A_1,A_2..等代表字段值
  • 只针对单表查询
  • 查询中不包含GROUP BY或者DISTINCT
  • SELECT查询的字段全部被包含在索引组成部分,即符合覆盖索引规范

 倒序索引

--添加倒序索引(表t2之前没有建立倒序索引)
mysql> alter table t2 add index idx_age_desc(age desc);
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0mysql> show create table t2;
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                     |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| t2    | CREATE TABLE `t2` (`id` int NOT NULL,`age` int DEFAULT NULL,KEY `idx_age` (`age`),KEY `idx_age_desc` (`age` DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

 注意:只有InnoDB存储引擎支持降序索引

隐藏索引

索引的维护是需要较大的成本的,数据量越大,建立索引花费的成本也就越大。但是,往往我们在调优的时候有这样的需求,我们想要看看禁用掉这个索引对查询性能的影响。

在8.0版本以前,都是要删除这个索引,之后发现这个索引有用,又要加回来,极大的增加了操作成本。所以隐藏索引最明显的一个作用类似索引回收站。

--创建(添加)索引的时候设置索引的隐藏属性
ALTER TABLE 表名 ADD INDEX 索引名(列名) INVISIBLE;
create index 索引名 on 表名(字段1,字段2....) INVISIBLE;--对已经存在的索引切换隐藏或者显示
ALTER TABLE 表名 ALTER INDEX 索引名 INVISIBLE;
ALTER TABLE 表名 ALTER INDEX 索引名 VISIBLE; 例子:
mysql> show index from t2;
Empty set (0.00 sec)mysql> create index idx_age on t2(age) INVISIBLE;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0--结果查看Visible字段
mysql> show index from t2;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| t2    |          1 | idx_age  |            1 | age         | A         |           0 |     NULL |   NULL | YES  | BTREE      |         |               | NO      | NULL       |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
1 row in set (0.01 sec)mysql> alter table t2 alter index idx_age VISIBLE;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0mysql> show index from t2;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| t2    |          1 | idx_age  |            1 | age         | A         |           0 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
1 row in set (0.00 sec)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/502742.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

USLE模型-LS因子的计算

目录 计算坡度计算填洼计算流向计算水流长度计算水平投影![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/75e015b2d6874ce9b6652f2b8730b90f.png)计算可变的坡度指数m计算坡长因子L计算坡度因子S计算LS因子参考视频 计算坡度 准备好30米分辨率的dem 计算填洼 计…

【Unity】如何设置Unity脚本的执行顺序?

在 Unity 编辑器中设置脚本执行顺序 在 Unity 中&#xff0c;如果有多个脚本&#xff0c;并且它们之间的执行顺序很重要&#xff0c;可以通过编辑器设置来确保它们按照自己期望的顺序执行。这对于确保某些脚本在其他脚本之前执行非常有用。在这篇文章中&#xff0c;将向会展示如…

农产品质量追溯系统—简介

概要 农产品质量安全事关广大人民群众的食用安全和身体健康。解决农产品质量安全问题,需要从源头开始抓好、抓实农产品安全监管工作。通过建立从产地到市场的全程质量控制系统和追溯制度,对农产品产地环境、生产过程、产品检测、包装盒标识等关键环节进行监督管理,提高广大…

electron+vue3全家桶+vite项目搭建【28】封装窗口工具类【2】窗口组,维护窗口关系

文章目录 引入实现效果思路主进程模块渲染进程模块测试效果 引入 demo项目地址 窗口工具类系列文章&#xff1a; 封装窗口工具类【1】雏形 我们思考一下窗口间的关系&#xff0c;窗口创建和销毁的一些动作&#xff0c;例如父子窗口&#xff0c;窗口组合等等&#xff0c;还有…

Redis--持久化机制详解

什么是redis持久化&#xff1f; Redis持久化是将内存的数据持久化到磁盘上&#xff0c;防止Redis宕机或者断点的时候内存中的数据丢失&#xff0c;把内存中的数据写入到磁盘的过程叫持久化。 Redis持久化的方式&#xff1f; RDB&#xff08;Redis DataBase&#xff09;&…

Vue+SpringBoot打造天然气工程运维系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统角色分类2.2 核心功能2.2.1 流程 12.2.2 流程 22.3 各角色功能2.3.1 系统管理员功能2.3.2 用户服务部功能2.3.3 分公司&#xff08;施工单位&#xff09;功能2.3.3.1 技术员角色功能2.3.3.2 材料员角色功能 2.3.4 安…

JAVA的学习日记

JAVA的学习日记&#xff08;2024.3.1&#xff09;&#xff08;b站韩顺平老师课程学习笔记版&#xff09; ps:捡起忘光光的Java语言 Sublime //1. public是公有&#xff0c;class是类 //2. public class Hello表示Hello是一个类&#xff0c;是一个public公有的类 //3. Hello{…

G8-ACGAN理论

本文为&#x1f517;365天深度学习训练营 中的学习记录博客 原作者&#xff1a;K同学啊|接辅导、项目定制 我的环境&#xff1a; 1.语言&#xff1a;python3.7 2.编译器&#xff1a;pycharm 3.深度学习框架Pytorch 1.8.0cu111 一、对比分析 前面的文章介绍了CGAN&#xf…

(定时器/计数器)中断系统(详解与使用)

讲解 简介 定时器/计数器 定时器实际上也是计数器,只是计数的是固定周期的脉冲 定时和计数只是触发来源不同(时钟信号和外部脉冲)其他方面是一样的。 定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔“一秒”,计数单元的数值就增加一,当计数单元数值…

多输入多输出 | Matlab实现RIME-BP霜冰算法优化BP神经网络多输入多输出预测

多输入多输出 | Matlab实现RIME-BP霜冰算法优化BP神经网络多输入多输出预测 目录 多输入多输出 | Matlab实现RIME-BP霜冰算法优化BP神经网络多输入多输出预测预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介绍 多输入多输出 | Matlab实现RIME-BP霜冰算法优化BP神经网…

Java SPI:Service Provider Interface

SPI机制简介 SPI&#xff08;Service Provider Interface&#xff09;&#xff0c;是从JDK6开始引入的&#xff0c;一种基于ClassLoader来发现并加载服务的机制。 一个标准的SPI&#xff0c;由3个组件构成&#xff0c;分别是&#xff1a; Service&#xff1a;是一个公开的接口…

线性规划在多种问题形式下的应用

线性规划的用处非常的广泛&#xff0c;这主要是因为很多类型的问题是可以通过转化的方式转化为线性规划的问题。例如需要再图论中寻找起始点到给定的点的最短路径问题&#xff1a; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 假设要计算从节点0到节点…