MySQL索引失效之隐式转换的问题

目录
  • 常见索引失效:
  • 一、常见索引失效场景
    • 1、条件字段函数操作
    • 2、条件字段运算操作
    • 3、隐式类型转换
    • 4、隐式字符编码转换
  • 二、类型转换
    • 1、字符串转整型
    • 2、时间类型转换

常见索引失效:

1. 条件索引字段"不干净":函数操作、运算操作

2. 隐式类型转换:字符串转数值;其他类型转换

3. 隐式字符编码转换:按字符编码数据长度大的方向转换,避免数据截取

一、常见索引失效场景

root@test 10:50 > show create table t_num\G
*************************** 1. row ***************************
       Table: t_num
Create Table: CREATE TABLE `t_num` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c1` int(11) NOT NULL,
  `c2` varchar(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `ix_c1` (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
​
root@test 10:51 > select * from t_num;
+----+----+----+
| id | c1 | c2 |
+----+----+----+
|  1 | -2 | -2 |
|  2 | -1 | -1 |
|  3 |  0 |  0 |
|  4 |  1 |  1 |
|  5 |  2 |  2 |
+----+----+----+
​
# 在c1字段上加上索引
root@test 10:52 > alter table t_num add index ix_c1(c1);
​
# 标准使用情况下,索引有效
root@test 10:55 > explain select * from t_num where c1 = -1;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t_num | NULL       | ref  | ix_c1         | ix_c1 | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+

1、条件字段函数操作

# 在where中c1上加上abs()绝对值函数,可以看到type=ALL,全表扫描,在Server层进行绝对值处理后进行比较
root@test 10:58 > explain select * from t_num where abs(c1) = 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_num | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    5 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

如上,对索引字段做函数操作,即where条件列上不干净时,可能会破坏索引值的有序性(按照c1的值有序组织索引树),因此优化器就决定放弃走索引树搜索功能。

但是,条件字段函数操作下,也并非完全的走全表扫描,优化器并非完全的放弃该字段索引。

# 选择查询的数据,只有id和c1字段,可以看到type=index,使用到了ix_c1索引
root@test 10:59 > explain select id,c1 from t_num where abs(c1) = 1;
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | t_num | NULL       | index | NULL          | ix_c1 | 4       | NULL |    5 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+--------------------------+

如上,由于ix_c1索引树是根节点c1和叶子节点id构造的,虽然因为c1上的函数操作导致放弃索引定位,但优化器可以选择遍历该索引树,使用覆盖索引(Using index),无需回表,将所需的id和c1数据返回Server层后进行后续的abs()和where过滤。

2、条件字段运算操作

# where条件里,对c1进行运算操作
root@test 11:03 > explain select * from t_num where c1 + 1 = 2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_num | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    5 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

如上,虽然“+1”的操作并没有破坏c1索引的有序性,但优化器仍然没有使用该索引快速定位。因此,等号左边,注意优化掉索引字段上的运算操作。

3、隐式类型转换

# 在c2字段上加上索引
root@test 12:30 > alter table t_num add index ix_c2(c2);
​
# 标准使用情况下(注:c2是varchar类型的),索引有效
root@test 12:30 > explain select * from t_num where c2 = "2";
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | t_num | NULL       | ref  | ix_c2         | ix_c2 | 42      | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
​
# 去掉等号右边值的引号,即字符串和数值进行比较,索引失效
root@test 12:30 > explain select * from t_num where c2 = 2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_num | NULL       | ALL  | ix_c2         | NULL | NULL    | NULL |    5 |    20.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+

如上,c2字段是varchar类型,是字符串和数值的比较,此时,MySQL是将字符串转换成数字,即此处的c2被CAST(c2 AS signed int),这就相当于对条件字段做了函数操作,优化器放弃走树索引定位。

4、隐式字符编码转换

# 创建一个t_cou表,表结构基本和前面的t_num相同,唯一不同的设置是表字符集CHARSET=utf8
root@test 14:02 > show create table t_cou\G
*************************** 1. row ***************************
       Table: t_cou
Create Table: CREATE TABLE `t_cou` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c1` int(11) NOT NULL,
  `c2` varchar(10) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `ix_c1` (`c1`),
  KEY `ix_c2` (`c2`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
​
root@test 14:02 > insert into t_cou select * from t_num;
​
# join表,t_num和t_cou通过c2字段进行关联查询
root@test 14:03 > select n.* from t_num n
    -> join t_cou c
    -> on n.c2 = c.c2
    -> where n.c1 = 1;
+----+----+----+
| id | c1 | c2 |
+----+----+----+
|  4 |  1 | 1  |
+----+----+----+
​
root@test 14:23 > explain select n.* from t_num n join t_cou c  on n.c2 = c.c2 where c.c1 = 1;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | c     | NULL       | ref  | ix_c1         | ix_c1 | 4       | const |    1 |   100.00 | NULL                  |
|  1 | SIMPLE      | n     | NULL       | ref  | ix_c2         | ix_c2 | 42      | func  |    1 |   100.00 | Using index condition |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-----------------------+
# 执行计划分析:
# 1.操作的c表,使用了ix_c1定位到一行数据
# 2.从c表定位到的行数据,拿到c2字段去操作n表,t_cou称为驱动表,t_num称为被驱动表
# 3.ref=func说明使用了函数操作,指的是n.c2=CONVERT(c.c2 USING utf8mb4)
# 4.同时Using index condition,ix_c2读取查询时,使用被下推的条件过滤,满足条件的才回表
​
root@test 14:23 > explain select n.* from t_num n join t_cou c  on n.c2 = c.c2 where n.c1 = 1;
+----+-------------+-------+------------+-------+---------------+-------+---------+-------+------+----------+-----------------------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key   | key_len | ref   | rows | filtered | Extra                                                           |
+----+-------------+-------+------------+-------+---------------+-------+---------+-------+------+----------+-----------------------------------------------------------------+
|  1 | SIMPLE      | n     | NULL       | ref   | ix_c1,ix_c2   | ix_c1 | 4       | const |    1 |   100.00 | NULL                                                            |
|  1 | SIMPLE      | c     | NULL       | index | NULL          | ix_c2 | 32      | NULL  |    5 |   100.00 | Using where; Using index; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+-------+---------------+-------+---------+-------+------+----------+-----------------------------------------------------------------+
# 执行计划分析:
# 1.操作的n表,使用了ix_c1定位到一行数据
# 2.从n表定位到的行数据,拿到c2字段去操作c表,t_num称为驱动表,t_cou称为被驱动表
# 3.同样的n.c2=c.c2,会将c.c2的字符集进行转换,即被驱动表的索引字段上加函数操作,索引失效
# 4.BNL,表join时,驱动表数据读入join buffer,被驱动表连接字段无索引则全表扫,每取一行和join buffer数据对比判断,作为结果集返回

如上,分别对t_num、 t_cou作为驱动表和被驱动表的执行计划分析,总结:

utf8mb4和utf8两种不同字符集(编码)类型的字符串在做比较时,MySQL会先把 utf8 字符串转成 utf8mb4 字符集,再做比较。为什么?字符集 utf8mb4 是 utf8 的超集,再做隐式自动类型转换时,为了避免数据在转换过程中由于截断导致数据错误,会“按数据长度增加的方向”进行转换。

表连接过程中,被驱动表的索引字段上加函数操作,会导致对被驱动表做全表扫描。

优化手法:

修改统一join字段的字符集

对驱动表下手,将连接字段的字符集转换成被驱动表连接字段的字符集

root@test 18:09 > explain select n.* from t_num n join t_cou c  on convert(n.c2 using utf8) = c.c2 where n.c1 = 1;
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra                    |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+--------------------------+
|  1 | SIMPLE      | n     | NULL       | ref  | ix_c1         | ix_c1 | 4       | const |    1 |   100.00 | NULL                     |
|  1 | SIMPLE      | c     | NULL       | ref  | ix_c2         | ix_c2 | 32      | func  |    1 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+--------------------------+

二、类型转换

1、字符串转整型

# 字符开头的一律为0
root@test 18:44 > select convert("abc", unsigned integer);
+----------------------------------+
| convert("abc", unsigned integer) |
+----------------------------------+
|                                0 |
+----------------------------------+
# 'abc' = 0是成立的,因此查询时等号右边使用对应的类型很重要,0匹配出字段字符开头数据,'0'只匹配0
root@test 18:44 > select 'abc' = 0;
+-----------+
| 'abc' = 0 |
+-----------+
|         1 |
+-----------+
​
# 数字开头的,直接截取到第一个不是字符的位置
root@test 18:45 > select convert("123abc", unsigned integer);
+-------------------------------------+
| convert("123abc", unsigned integer) |
+-------------------------------------+
|                                 123 |
+-------------------------------------+

2、时间类型转换

root@test 19:11 > show create table time_demo\G
*************************** 1. row ***************************
       Table: time_demo
Create Table: CREATE TABLE `time_demo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c1` datetime DEFAULT NULL,
  `c2` date DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `ix_c1` (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4
​
root@test 19:15 > select count(*) from time_demo;
+----------+
| count(*) |
+----------+
|       11 |
+----------+
​
root@test 19:16 > select * from time_demo limit 4;
+----+---------------------+------------+
| id | c1                  | c2         |
+----+---------------------+------------+
|  1 | 2022-01-08 00:01:01 | 2022-01-08 |
|  2 | 2022-01-06 23:01:01 | 2022-01-06 |
|  3 | 2022-01-06 00:00:00 | 2022-01-06 |
|  4 | 2022-01-08 00:00:00 | 2022-01-08 |
+----+---------------------+------------+
​
# 1.date转datetime:末尾追加 00:00:00
root@test 19:11 > select * from time_demo where c1 between "2022-01-06" and "2022-01-08";
+----+---------------------+------------+
| id | c1                  | c2         |
+----+---------------------+------------+
|  2 | 2022-01-06 23:01:01 | 2022-01-06 |
|  3 | 2022-01-06 00:00:00 | 2022-01-06 |
|  4 | 2022-01-08 00:00:00 | 2022-01-08 |
+----+---------------------+------------+
# 结果分析:c1是datetime类型,进行比较时,between and中的date类型会转换成datetime
# 即 where c1 between "2022-01-06 00:00:00" and "2022-01-08 00:00:00";
# 同 where c1 >= "2022-01-06 00:00:00" and c1 <= "2022-01-08 00:00:00";
root@test 19:42 > explain select * from time_demo where c1 between "2022-01-06" and "2022-01-08";
+----+-------------+-----------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
| id | select_type | table     | partitions | type  | possible_keys | key   | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-----------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | time_demo | NULL       | range | ix_c1         | ix_c1 | 6       | NULL |    3 |   100.00 | Using index condition |
+----+-------------+-----------+------------+-------+---------------+-------+---------+------+------+----------+-----------------------+
# 格式化date转datetime
root@test 19:23 > select date_format("2022-01-08","%Y-%m-%d %H:%i:%s");
+-----------------------------------------------+
| date_format("2022-01-08","%Y-%m-%d %H:%i:%s") |
+-----------------------------------------------+
| 2022-01-06 00:00:00                           |
+-----------------------------------------------+
​
# 2.datetime转date:直接截取date部分
root@test 19:47 > select date(c1) from time_demo limit 1;
+------------+
| date(c1)   |
+------------+
| 2022-01-06 |
+------------+
​
# 3.date转time,没有意义,直接变成 00:00:00

到此这篇关于MySQL索引失效之隐式转换的问题的文章就介绍到这了,更多相关MySQL 隐式转换内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 解析MySQL隐式转换问题

    一.问题描述 root@mysqldb 22:12: [xucl]> show create table t1\G *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.0

  • MySQL令人大跌眼镜的隐式转换

    目录 MySQL的隐式转换 一.问题描述 二.源码解释 三.结论 MySQL的隐式转换 一.问题描述 show create table t1\G *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1

  • Mysql 5.6 "隐式转换"导致的索引失效和数据不准确的问题

    背景 在一次进行SQl查询时,我试着对where条件中vachar类型的字段去掉单引号查询,这个时候发现这条本应该很快的语句竟然很慢.这个varchar字段有一个复合索引.其中的总条数有58989,甚至不加单引号查出来的数据不是我们想要的数据. 使用的是mysql 5.6版本,innoDB引擎 实际情况如下 下面我们来看一下执行的结果 在上面的描述中我们还得注意就是,你的where条件的字符串不加单引号必须是全数字.不然就会报错 还有可能查出来的数据不是我们想要的数据.如下图 分析 从执行结果来

  • 谈谈MySQL中的隐式转换

    工作过程中会遇到比较多关于隐式转换的案例,隐式转换除了会导致慢查询,还会导致数据不准.本文通过几个生产中遇到的案例来. 基础知识 关于比较运算的原则,MySQL官方文档的描述: https://dev.mysql.com/doc/refman/5.6/en/type-conversion.html 如果 判断符号左右两边有一个为NULL,结果就是null,除非使用安全的等值判断 <=> (none) 05:17:16 >select  null = null; +------------

  • 浅谈MySql整型索引和字符串索引失效或隐式转换问题

    目录 问题概述 问题重现 问题引申 结论 问题概述 今天在上班时,DBA突然找出来一段sql,表示该sql存在隐式转换,不走索引.经过我们的查看后,发现是类型varchar的字段, 我们使用条件传入了数值型的值,由于担心违反保密协议,在此就不贴图了,由我重现一下类似情况给大家看一下. 问题重现 首先我们先创建一张用户表test_user,其中USER_ID为了效果我们设置为varchar类型且加上唯一索引. CREATE TABLE test_user ( ID int(11) NOT NULL

  • MySQL索引失效之隐式转换的问题

    目录 常见索引失效: 一.常见索引失效场景 1.条件字段函数操作 2.条件字段运算操作 3.隐式类型转换 4.隐式字符编码转换 二.类型转换 1.字符串转整型 2.时间类型转换 常见索引失效: 1. 条件索引字段"不干净":函数操作.运算操作 2. 隐式类型转换:字符串转数值:其他类型转换 3. 隐式字符编码转换:按字符编码数据长度大的方向转换,避免数据截取 一.常见索引失效场景 root@test 10:50 > show create table t_num\G ******

  • MySQL优化案例之隐式字符编码转换

    目录 索性失效前提 一个真实的案例 优化前原始sql分析 优化初步处理 初步优化无效分析 第二次优化处理 第三次优化 结论 索性失效前提 MySQL中我们知道有: 1.如果对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能. 2.隐式类型转换也会导致同样的放弃走树搜索. 因为类型转换等价于在条件字段上使用了函数比如: /*假设tradeid字段有索引,且为varchar类型*/ mysql> select * from tradelog where tradeid=

  • MySQL索引失效的典型案例

    典型案例 有两张表,表结构如下: CREATE TABLE `student_info` (   `id` int(11) NOT NULL,   `name` varchar(10) DEFAULT NULL,   PRIMARY KEY (`id`),   KEY `idx_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 CREATE TABLE `student_score` (   `id` int(11) NOT NULL,

  • MySQL索引失效原理

    目录 1.索引失效原因 2.再来看看哪些情况会破坏索引的有序性. - 对索引字段做函数操作 - 隐式类型转换 - 隐式字符编码转换 3.总结 1.索引失效原因 首先看看哪些情况下,将会导致查找不能利用索引的有序性. 假设一个表test中有a,b,c,d四个字段,c是主键. 在a,b字段上建立联合索引(a,b):CREATE index idx_a_b on test(a,b); B+树联合索引.JPG 可以得到的规律是:优先按a字段从小到大排序,a字段相等的按b字段从小到大排序: 分析以下情况,

  • 分享15个Mysql索引失效的场景

    目录 背景 数据库及索引准备 创建表结构 初始化数据 数据库版本及执行计划 1 联合索引不满足最左匹配原则 2 使用了select * 3 索引列参与运算 4 索引列参使用了函数 5 错误的Like使用 6 类型隐式转换 7.使用OR操作 8 两列做比较 9 不等于比较 10 is not null 11 not in和not exists 12 order by导致索引失效 13 参数不同导致索引失效 14 其他 小结 背景 无论你是技术大佬,还是刚入行的小白,时不时都会踩到Mysql数据库不

随机推荐