SQL优化案例(1):隐式转换

简介: 在实际的开发运维过程中,想必大家也常常会碰到慢SQL的困扰。一条性能不好的SQL,往往会带来过大的性能开销,进而引起整个操作系统资源的过度使用,甚至造成会话堆积,引发线上故障。而在SQL调优的场景中,一类比较常见的问题,就是隐式类型转换。那什么是隐式转换呢?

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

在实际的开发运维过程中,想必大家也常常会碰到慢SQL的困扰。一条性能不好的SQL,往往会带来过大的性能开销,进而引起整个操作系统资源的过度使用,甚至造成会话堆积,引发线上故障。
而在SQL调优的场景中,一类比较常见的问题,就是隐式类型转换。那什么是隐式转换呢?

在MySQL中,当操作符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容,此时则会发生隐式转换。出现隐式转换,往往意味着SQL的执行效率将大幅降低。
接下来笔者将结合几大常见场景,让大家实际体会什么是隐式转换,以及如何去应对出现隐式转换的情况,请阅读以下案例。

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

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

1) 待优化场景

SQL及执行计划如下:

select * from dt_t1 where emp_no = 41680;

image.png

该表索引如下:

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类型时,便可以正常使用到索引了,优化效果如下:

image.png

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

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

1) 待优化场景

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

image.png

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`

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

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

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

1) 待优化场景

SQL及执行计划如下:

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

image.png

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

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

image.png

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

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

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

1) 待优化场景

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

该SQL执行计划如下:

image.png

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性能问题时,分析相应场景对症下药即可。

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

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
SQL 数据库 数据安全/隐私保护
数据库数据恢复——sql server数据库被加密的数据恢复案例
SQL server数据库数据故障: SQL server数据库被加密,无法使用。 数据库MDF、LDF、log日志文件名字被篡改。 数据库备份被加密,文件名字被篡改。
|
SQL 关系型数据库 MySQL
MySQL进阶突击系列(07) 她气鼓鼓递来一条SQL | 怎么看执行计划、SQL怎么优化?
在日常研发工作当中,系统性能优化,从大的方面来看主要涉及基础平台优化、业务系统性能优化、数据库优化。面对数据库优化,除了DBA在集群性能、服务器调优需要投入精力,我们研发需要负责业务SQL执行优化。当业务数据量达到一定规模后,SQL执行效率可能就会出现瓶颈,影响系统业务响应。掌握如何判断SQL执行慢、以及如何分析SQL执行计划、优化SQL的技能,在工作中解决SQL性能问题显得非常关键。
|
11月前
|
SQL 存储 监控
SQL日志优化策略:提升数据库日志记录效率
通过以上方法结合起来运行调整方案, 可以显著地提升SQL环境下面向各种搜索引擎服务平台所需要满足标准条件下之数据库登记作业流程综合表现; 同时还能确保系统稳健运行并满越用户体验预期目标.
492 6
|
SQL 存储 自然语言处理
SQL的解析和优化的原理:一条sql 执行过程是什么?
SQL的解析和优化的原理:一条sql 执行过程是什么?
SQL的解析和优化的原理:一条sql 执行过程是什么?
|
SQL 关系型数据库 MySQL
如何优化SQL查询以提高数据库性能?
这篇文章以生动的比喻介绍了优化SQL查询的重要性及方法。它首先将未优化的SQL查询比作在自助餐厅贪多嚼不烂的行为,强调了只获取必要数据的必要性。接着,文章详细讲解了四种优化策略:**精简选择**(避免使用`SELECT *`)、**专业筛选**(利用`WHERE`缩小范围)、**高效联接**(索引和限制数据量)以及**使用索引**(加速搜索)。此外,还探讨了如何避免N+1查询问题、使用分页限制结果、理解执行计划以及定期维护数据库健康。通过这些技巧,可以显著提升数据库性能,让查询更高效流畅。
|
关系型数据库 MySQL 大数据
大数据新视界--大数据大厂之MySQL 数据库课程设计:MySQL 数据库 SQL 语句调优的进阶策略与实际案例(2-2)
本文延续前篇,深入探讨 MySQL 数据库 SQL 语句调优进阶策略。包括优化索引使用,介绍多种索引类型及避免索引失效等;调整数据库参数,如缓冲池、连接数和日志参数;还有分区表、垂直拆分等其他优化方法。通过实际案例分析展示调优效果。回顾与数据库课程设计相关文章,强调全面认识 MySQL 数据库重要性。为读者提供综合调优指导,确保数据库高效运行。
|
SQL 关系型数据库 MySQL
基于SQL Server / MySQL进行百万条数据过滤优化方案
对百万级别数据进行高效过滤查询,需要综合使用索引、查询优化、表分区、统计信息和视图等技术手段。通过合理的数据库设计和查询优化,可以显著提升查询性能,确保系统的高效稳定运行。
980 9
|
SQL 数据库
数据库数据恢复—SQL Server报错“错误 823”的数据恢复案例
SQL Server数据库附加数据库过程中比较常见的报错是“错误 823”,附加数据库失败。 如果数据库有备份则只需还原备份即可。但是如果没有备份,备份时间太久,或者其他原因导致备份不可用,那么就需要通过专业手段对数据库进行数据恢复。
1054 2
|
SQL Oracle 关系型数据库
如何在 Oracle 中配置和使用 SQL Profiles 来优化查询性能?
在 Oracle 数据库中,SQL Profiles 是优化查询性能的工具,通过提供额外统计信息帮助生成更有效的执行计划。配置和使用步骤包括:1. 启用自动 SQL 调优;2. 手动创建 SQL Profile,涉及收集、执行调优任务、查看报告及应用建议;3. 验证效果;4. 使用 `DBA_SQL_PROFILES` 视图管理 Profile。
|
SQL Oracle 数据库
使用访问指导(SQL Access Advisor)优化数据库业务负载
本文介绍了Oracle的SQL访问指导(SQL Access Advisor)的应用场景及其使用方法。访问指导通过分析给定的工作负载,提供索引、物化视图和分区等方面的优化建议,帮助DBA提升数据库性能。具体步骤包括创建访问指导任务、创建工作负载、连接工作负载至访问指导、设置任务参数、运行访问指导、查看和应用优化建议。访问指导不仅针对单条SQL语句,还能综合考虑多条SQL语句的优化效果,为DBA提供全面的决策支持。
491 11