MySQL是当下最流行的关系型数据库之一,互联网高速发展的今天,MySQL数据库在电商、金融等诸多行业的生产系统中被广泛使用。

在实际的开发运维过程中,想必大家也常常会碰到慢SQL的困扰。一条性能不好的SQL,往往会带来过大的性能开销,进而引起整个操作系统资源的过度使用,甚至造成会话堆积,引发线上故障。

而在SQL调优的场景中,一类比较常见的问题,就是隐式类型转换。那什么是隐式转换呢?

在MySQL中,当操作符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容,此时则会发生隐式转换。出现隐式转换,往往意味着SQL的执行效率将大幅降低。

接下来笔者将结合几大常见场景,让大家实际体会什么是隐式转换,以及如何去应对出现隐式转换的情况,请阅读以下案例。

传递数据类型和字段类型不一致造成隐式转换

一类比较经典的场景就是传递数据类型和字段类型不一致造成的隐式转换,这种场景也是我们平时最常遇到的。具体可以看下下面这个例子:

1) 待优化场景

SQL及执行计划如下:

select * from dt_t1 where emp_no = 41680;

该表索引如下:

key idx_empno (`emp_no`)

2)场景解析

从执行计划中Type部分:ALL,全表扫描,而没有走idx_empno索引, 一般这种情况可能传递的数据类型和实际的字段类型不一致,那么我们来看下具体的表结构。

root@localhost mysql.sock 5.7.28-log :[employees] 14:48:10>desc employees;
+------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------+------+-----+---------+-------+
| emp_no | varchar(14) | NO | MUL | NULL | |
| birth_date | date | NO | | NULL | |
| first_name | varchar(14) | NO | | NULL | |
| last_name | varchar(16) | NO | | NULL | |
| gender | enum('M','F') | NO | | NULL | |
| hire_date | date | NO | | NULL | |
+------------+---------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

表结构中看到该字段类型为varchar 类型,传递字段为整型,造成隐式转换不能走索引。

3)场景优化

该SQL可通过简单改写来避免出现隐式转换,如下:

select * from dt_t1 where emp_no='41680';

当传入数据是与匹配字段一致的varchar类型时,便可以正常使用到索引了,优化效果如下:

关联字段类型不一致造成隐式转换

除了常量匹配的查询场景,关联查询在关联字段不一致的情况下,也会出现隐式转换。

1) 待优化场景

SELECT  count(*) from t1  as a
JOIN `t2` b on a.`id` = b.`alipay_order_no` ;

2)场景解析

从执行计划中可以看出被驱动表 b, Extra:Range checked for each record (index map: 0x8)

一般在当我们看到Range checked for each record (index map: 0x8) 的时候,可能就是发生了隐式转换,我们来看下官方文档是怎么解释的

Range checked for each record (index map: N) (JSON property: message)
MySQL found no good index to use, but found that some of indexes might be used after column values from preceding tables are known. For each row combination in the preceding tables, MySQL checks whether it is possible to use a range or index_merge access method to retrieve rows. This is not very fast, but is faster than performing a join with no index at all. The applicability criteria are as described in Section 8.2.1.2, “Range Optimization”, and Section 8.2.1.3, “Index Merge Optimization”, with the exception that all column values for the preceding table are known and considered to be constants.
Indexes are numbered beginning with 1, in the same order as shown by SHOW INDEX for the table. The index map value N is a bitmask value that indicates which indexes are candidates. For example, a value of 0x19 (binary 11001) means that indexes 1, 4, and 5 will be considered.

查看下表结构:

CREATE TABLE `t2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`alipay_order_no` varchar(45) DEFAULT NULL,
xxxx
PRIMARY KEY (`id`),
KEY `idx_alipay_order_no_temp` (`alipay_order_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2539968 DEFAULT CHARSET=utf8
共返回 1 行记录,花费 5 ms.
CREATE TABLE `t1` (
`id` bigint(20) NOT NULL,
xxxxxx
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
共返回 1 行记录,花费 5 ms.

我们从表结构上面进行观察到该关联字段数据 一个是int 类型,一个是varchar 类型。

当发生这种场景的时候我们应该如何优化呢?

我们还回来看看下具体的执行计划,该驱动表为a,被驱动表b; 关联条件:a.id = b.alipay_order_no ; 当a 表的字段id 当为常数传递给b.alipay_order_no 的时候,发生column_type 不一致,无法使用索引,那么我们让a.id 传递的 字段类型和b.alipay_order_no 保持一致,就可以使用索引了?

3)场景优化

我们可以对驱动表的关联字段进行显式的类型转换,让其与被驱动表关联字段类型一致。改写后SQL如下:

SELECT  count(*)
from `t1`a
JOIN `t2` b on CAST( a.`id` AS CHAR ) = b.`alipay_order_no`

进行改写后就可以正常利用索引进行关联了,执行计划如下:

字符集不一致造成隐式转换

前面的两种场景都是操作符两侧数据类型不同的情况,事实上,数据类型相同也可能会出现隐式转换,比如下面这个字符集不一致导致隐式转换的例子:

1) 待优化场景

SQL及执行计划如下:

SELECT COUNT(*)
FROM `t1` o
join `t2` og ON `o`.`def8`= `og`.`group_id`
WHERE o.`def1`= 'DG21424956'

2)场景解析

从这个执行计划中我们可以看出第二列表og 中含有using join buffer (Block Nested Loop) ,TYpe=ALL .

一般这种情况下:using join buffer (Block Nested Loop) ,发生的情况是 a. 关联字段没有索引 b.发生隐式转换 等

看下具体表结构:

create table t1(
.....
`group_id` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
create table t2(
.....
`def8` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_tr_def1` (`def8`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

我们从表结构中可以看出关联字段都存在索引,但字符集是不一样的,t1 utf8,t2 utf8mb4.

3)场景优化

SQL改写思路和上例类似,我们对驱动表的关联字段进行字符集转换,如下:

SELECT COUNT(*)   FROM `t1`  o
left join `t2` og ON CONVERT( o.`def8` USING utf8 ) = `og`.`group_id`
WHERE o.`def1`= 'DG21424956

转换成一致的字符集之后,便可以通过索引进行关联了

校验规则不一致造成隐式转换

那么,只要保证操作符两侧数据类型以及字符集一致,就不会出现隐式转换吗?

答案是否定的,因为字符集还有一个很重要的属性,就是校验规则,当校验规则不一致的时候,也是会出现隐式转换行为的。具体看下面这个例子:

1) 待优化场景

SELECT *
FROM `t1`
WHERE `uuid` in (SELECT uuid FROM t2 WHERE project_create_at!= "0000-00-00 00:00:00")

该SQL执行计划如下:

2)场景解析

两张表的表结构如下:

CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT, `
uuid` varchar(128) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'UUID',
xxxxxx
PRIMARY KEY (`id`),
UNIQUE KEY `uuid_idx` (`uuid`)
) ENGINE=InnoDB AUTO_INCREMENT=2343994 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
CREATE TABLE `t2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uuid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '项目uuid',
PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=5408 DEFAULT CHARSET=utf8

我们从表结构看出,t1表作为被驱动表uuid是存在唯一索引的,并且关联字段数据类型以及字符集也都是一致的,但是校验规则的不同导致了这个场景无法使用到索引。

3)场景优化

我们可以通过如下改写,对驱动表关联字段的校验规则进行显示定义,让其与被驱动表一致

explain extended
select b.*
from (select uuid COLLATE utf8_unicode_ci as uuid
from t1 where project_create_at != "0000-00-00 00:00:00") a, t2 b
where a.uuid = b.uuid
+--------------+-----------------------+--------------------+----------------+-----------------------+-------------------+---------------+----------------+-----------------------+
| id | select_type | table | type | key | key_len | ref | rows | Extra |
+--------------+-----------------------+--------------------+----------------+-----------------------+-------------------+---------------+----------------+-----------------------+
| 1 | PRIMARY | <derived2> | ALL | | | | 51 | |
| 1 | PRIMARY | b | eq_ref | uuid_idx | 386 | a.uuid | 1 | |
| 2 | DERIVED | volunteer_patients | range | idx-project-create-at | 6 | | 51 | Using index condition |
+--------------+-----------------------+--------------------+----------------+-----------------------+-------------------+---------------+----------------+-----------------------+
共返回 3 行记录,花费 4 ms.

可以看到,改写后的SQL,正常使用到索引进行字段关联,这样就达到了我们预期的效果。

总结

隐式转换出现的场景主要有字段类型不一致、关联字段类型不一致、字符集类型不一致或校对规则不一致等。当出现隐式转换带来的SQL性能问题时,分析相应场景对症下药即可。

除此之外,隐式转换还可能会带来查询结果集不准,字符集不一致也会造成主从同步报错等,因此在实际使用时我们应当尽量避免。

更多技术型文章可关注公众号“云掣YUNCHE”

也可到官网作进一步了解:https://www.dtstack.com/dtsmart/

SQL优化案例(1):隐式转换的更多相关文章

  1. 数栈SQL优化案例:隐式转换

    MySQL是当下最流行的关系型数据库之一,互联网高速发展的今天,MySQL数据库在电商.金融等诸多行业的生产系统中被广泛使用. 在实际的开发运维过程中,想必大家也常常会碰到慢SQL的困扰.一条性能不好 ...

  2. MySQL性能优化:MySQL中的隐式转换造成的索引失效

    数据库优化是一个任重而道远的任务,想要做优化必须深入理解数据库的各种特性.在开发过程中我们经常会遇到一些原因很简单但造成的后果却很严重的疑难杂症,这类问题往往还不容易定位,排查费时费力最后发现是一个很 ...

  3. 大数据技术之_16_Scala学习_06_面向对象编程-高级+隐式转换和隐式值

    第八章 面向对象编程-高级8.1 静态属性和静态方法8.1.1 静态属性-提出问题8.1.2 基本介绍8.1.3 伴生对象的快速入门8.1.4 伴生对象的小结8.1.5 最佳实践-使用伴生对象解决小孩 ...

  4. MySQL SQL优化之字符串索引隐式转换

    之前有用户很不解:SQL语句非常简单,就是select * from test_1 where user_id=1 这种类型,而且user_id上已经建立索引了,怎么还是查询很慢? test_1的表结 ...

  5. SQL Server 隐式转换引发的躺枪死锁-程序员需知

    在SQL Server的应用开发过程(尤其是二次开发)中可能由于开发人员对表的结构不够了解,造成开发过程中使用了不合理的方式造成数据库引擎未按预定执行,以致影响业务.这是非常值得注意的.这次为大家介绍 ...

  6. SQL Server 隐式转换引发的死锁

    在SQL Server的应用开发过程(尤其是二次开发)中可能由于开发人员对表的结构不够了解,造成开发过程中使用了不合理的方式造成数据库引擎未按预定执行,以致影响业务.这是非常值得注意的.这次为大家介绍 ...

  7. SQL Server有意思的数据类型隐式转换问题

    写这篇文章的时候,还真不知道如何取名,也不知道这个该如何将其归类.这个是同事遇到的一个案例,案例比较复杂,这里抽丝剥茧,仅仅构造一个简单的案例来展现一下这个问题.我们先构造测试数据,如下所示: CRE ...

  8. SQL Server中提前找到隐式转换提升性能的办法

        http://www.cnblogs.com/shanksgao/p/4254942.html 高兄这篇文章很好的谈论了由于数据隐式转换造成执行计划不准确,从而造成了死锁.那如果在事情出现之前 ...

  9. 【转】SQL SERVER标量表达式的隐式转换

    在SQL Server中的数据类型中,存在着优先级的问题.标量表达示的返回结果类型也会根据操作数的类型而定,如1 +'1'=2.而不是'11',因些Int型的优先级比VARCHAR型的优先级要高.所以 ...

  10. 大数据学习day17------第三阶段-----scala05------1.Akka RPC通信案例改造和部署在多台机器上 2. 柯里化方法 3. 隐式转换 4 scala的泛型

    1.Akka RPC通信案例改造和部署在多台机器上  1.1 Akka RPC通信案例的改造(主要是把一些参数不写是) Master package com._51doit.akka.rpc impo ...

随机推荐

  1. Spring Boot RestController接口如何输出到终端

    背景 公司项目的批处理微服务,一般是在晚上固定时段通过定时任务执行,但为了预防执行失败,我们定义了对应的应急接口,必要时可以通过运维在终端中进行curl操作.然而,部分任务耗时较长,curl命令执行后 ...

  2. 创建及管理DSW实例

      机器学习PAI 产品概述 快速入门 操作指南 准备工作 工作空间管理 AI计算资源管理 AI开发 开发流程 快速开始 智能标注(iTAG) 可视化建模(PAI-Designer) 交互式建模(PA ...

  3. Solr Shiro Log4j2 命令执行--文件读取--反序列化--身份权限绕过--命令执行

    Solr Shiro Log4j2 命令执行--文件读取--反序列化--身份权限绕过--命令执行 solr 远程命令执行 (CVE-2019-17558) 漏洞简介 Apache Velocity是一 ...

  4. ESP32-MicroPython without Thonny

    why witout Thonny? 最近闲来在ESP32上用MicroPython捣鼓些小玩具,见很多教程推荐使用Thonny.欣然往之,竟是个这,实在不能认同.Thonny esp32-Micro ...

  5. DHorse v1.4.2 发布,基于 k8s 的发布平台

    版本说明 优化特性 在集群列表增加集群版本: 修改Jvm的GC指标名: 解决问题 解决shell脚本换行符的问题: 解决部署历史列表页,环境名展示错误的问题: 解决指标收集功能的异常: 升级指南 升级 ...

  6. python爬虫入门(1)-开发环境配置

    所谓的爬虫,就是通过模拟点击浏览器发送网络请求,接收站点请求响应,获取互联网信息的一组自动化程序. 也就是,只要浏览器(客户端)能做的事情,爬虫都能够做.      现在的互联网大数据时代,给予我们的 ...

  7. QMainWindow无法显示,使用show()不显示窗口(QT)

    当使用 MainWindow w: w.show(); 不显示窗口时 变更为: MainWindow *w=new MainWindow(); w->show();

  8. MySQL系列:binlog日志详解(参数、操作、GTID、优化、故障演练)

    目录 简介 作用 系统参数 --log_bin --server_id --binlog_format --sync-binlog(双一标准) --gtid-mode(gtid) --enforce- ...

  9. 【scipy 基础】--插值

    插值运算是一种数据处理方法,主要用来填补数据之间的空白或缺失值.因为在实际应用中,数据往往不是完整的,而是存在着空白或缺失值,这些空白或缺失值可能是由于数据采集困难.数据丢失或数据处理错误等原因造成的 ...

  10. gitlab : You won`t be able to pull or push project code via SSH until you add an SSH key to your profile(导致的问题:合并不了代码)

    gitlab : You won`t be able to pull or push project code via SSH until you add an SSH key to your pro ...