条件字段有索引但查询过程没使用索引的情况总结

来自慕课网专栏《一线数据库工程师带你深入理解MySQL》

目录

  • 创建测试表
  • 1 函数操作
  • 2 隐式转换
  • 3 模糊查询
  • 4 范围查询
  • 5 计算操作

创建测试表

CREATE TABLE `t1` (             /* 创建表t1 */
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a` varchar(20) DEFAULT NULL,
  `b` int(20) DEFAULT NULL,
  `c` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,  
  PRIMARY KEY (`id`),
  KEY `idx_a` (`a`) USING BTREE,
  KEY `idx_b` (`b`) USING BTREE,
  KEY `idx_c` (`c`) USING BTREE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4;

drop procedure if exists insert_t1; /* 如果存在存储过程insert_t1,则删除 */
delimiter ;;
create procedure insert_t1()        /* 创建存储过程insert_t1 */
begin
  declare i int;                    /* 声明变量i */
  set i=1;                          /* 设置i的初始值为1 */
  while(i<=10000)do                 /* 对满足i<=10000的值进行while循环 */
    insert into t1(a,b) values(i,i);  /* 写入表t1中a、b两个字段,值都为i当前的值 */
    set i=i+1;                        /* 将i加1 */
  end while;
end;;
delimiter ;
call insert_t1();                    /* 运行存储过程insert_t1 */

update t1 set c = '2019-05-22 00:00:00';  /* 更新表t1的c字段,值都为'2019-05-22 00:00:00' */
update t1 set c = '2019-05-21 00:00:00' where id=10000;	 /* 将id为10000的行的c字段改为与其它行都不一样的数据,以便后面实验使用 */

1 函数操作

mysql> explain select id,a,b from t1 where date_format(c,'%Y-%m-%d')=date_format('2019-05-21', '%Y-%m-%d') ;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 10302 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+

ref = null,rows = 10302,全盘扫描,索引并没有起作用。
原因:对条件字段做函数操作走不了索引。索引树中存储的是列的实际值和主键值。如果拿 ‘2019-05-21’ 去匹配,将无法定位到索引树中的值,因为值为'2019-05-22 00:00:00'。因此放弃走索引,而选择全表扫描。

解决办法

改成 c 字段实际值相匹配的形式,因此可以改成范围查询

mysql> explain select * from t1 where c>='2019-05-21 00:00:00' and c<='2019-05-21 23:59:59';
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | t1    | NULL       | range | idx_c         | idx_c | 5       | NULL |    1 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+

2 隐式转换

mysql> explain select * from t1 where a=1000;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | idx_a         | NULL | NULL    | NULL | 10302 |    10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+

ref = null,rows = 10302,全盘扫描,索引并没有起作用。

原因:
mysql> desc t1;
+-------+-------------+------+-----+-------------------+-------------------+
| Field | Type        | Null | Key | Default           | Extra             |
+-------+-------------+------+-----+-------------------+-------------------+
| id    | int(11)     | NO   | PRI | NULL              | auto_increment    |
| a     | varchar(20) | YES  | MUL | NULL              |                   |
| b     | int(20)     | YES  | MUL | NULL              |                   |
| c     | datetime    | NO   | MUL | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
+-------+-------------+------+-----+-------------------+-------------------+
a是 varchar(20),语句中 a 字段条件值没加单引号,导致 MySQL 内部会先把a转换成int型,再去做判断。

解决办法

索引字符串列条件添加单引号

mysql> explain select * from t1 where a='1000';
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ref  | idx_a         | idx_a | 83      | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+

经验分享:
建议在写SQL时,先看字段类型,然后根据字段类型写SQL。

3 模糊查询

mysql> explain select * from t1 where a like '%1111%';
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 10302 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+

解决办法

修改业务,让模糊查询必须包含条件字段前面的值

mysql> explain select * from t1 where a like '1111%';
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | t1    | NULL       | range | idx_a         | idx_a | 83      | NULL |    1 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+

经验分享:
如果条件只知道中间的值,需要模糊查询去查,那就建议使用ElasticSearch或其它搜索服务器。

4 范围查询

mysql> explain select * from t1 where b>=1 and b <=3000;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | idx_b         | NULL | NULL    | NULL | 10302 |    29.12 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+

原因:优化器会根据检索比例、表大小、I/O块大小等进行评估是否使用索引。比如单次查询的数据量过大,优化器将不走索引。

解决办法

降低单次查询范围,分多次查询:

mysql> explain select * from t1 where b>=1 and b <=1000;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | t1    | NULL       | range | idx_b         | idx_b | 5       | NULL | 1000 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
mysql> explain select * from t1 where b>=1001 and b <=2000;
mysql> explain select * from t1 where b>=2001 and b <=3000;

经验分享:
实际这种范围查询而导致使用不了索引的场景经常出现,比如按照时间段抽取全量数据,每条SQL抽取一个月的;或者某张业务表历史数据的删除。
遇到此类操作时,应该在执行之前对SQL做explain分析,确定能走索引,再进行操作,否则不但可能导致操作缓慢,在做更新或者删除时,甚至会导致表所有记录锁住,十分危险。

5 计算操作

mysql> explain select * from t1 where b>=1 and b <=3000;
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | idx_b         | NULL | NULL    | NULL | 10302 |    29.12 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+

原因:对索引字段做运算将使用不了索引。

解决办法

将计算操作放在等号后面:

mysql> explain select * from t1 where b =1000 + 1;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t1    | NULL       | ref  | idx_b         | idx_b | 5       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+

经验分享:
一般需要对条件字段做计算时,建议通过程序代码实现,而不是通过MySQL实现。如果在MySQL中计算的情况避免不了,那必须把计算放在等号后面。

你可能感兴趣的:(玩转SQL,mysql,sql,数据库)