隐秘的 MySQL 类型转换

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 近期工作中同事遇到的一个真实问题,稍作整理后分享给大家~

近期工作中同事遇到的一个真实问题,稍作整理后分享给大家~


1、问题开篇


一张用户表 `users` ,其中字段 `phone` 添加了普通索引。


CREATE TABLE users (
  id bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  name varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
  phone varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '手机',
  created_at timestamp NOT NULL DEFAULT '1970-01-01 16:00:00' COMMENT '创建时间',
  updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (id),
  KEY idx_phone (phone)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';


分别执行以下SQL:


1)字符串类型查询


EXPLAIN SELECT * FROM users WHERE phone = '2';


执行计划如下:

微信图片_20220607211035.png


2)数值型查询


EXPLAIN SELECT * FROM users WHERE phone = 2;


执行计划如下:


微信图片_20220607211039.png


发现问题:


当索引字段 `phone` 为字符串类型时,字符串查询时候使用了索引`idx_phone`,而数值类型查询时候竟无法使用索引`idx_phone`。


2、问题引申


假如索引字段为整型的话,那用字符串查询时会不会走索引呢?


实践出真知,我们来验证一下。


同样如上表,修改字段 `phone` 类型由 varchar 变更为 bigint:


ALTER TABLE users MODIFY COLUMN phone bigint(16) NOT NULL COMMENT '手机';


然后,分别执行以下SQL:


1)字符串类型查询


EXPLAIN SELECT * FROM users WHERE phone = '2';


执行计划如下:


微信图片_20220607211041.png


2)数值型查询


EXPLAIN SELECT * FROM users WHERE phone = 2;


执行计划如下:

微信图片_20220607211044.png

执行后发现,无论是以字符串查询还是以数值型查询都会用到索引。


小结:


  • 当索引字段是数值类型时,数值型或者字符型查询都不影响索引的使用。


  • 当索引字段是字符类型时,数值型查询无法使用索引,字符型查询可正常使用索引。


3、跟进探究


为什么会是这样呢?其根源就是MySQL的隐式类型转换


3.1 什么是隐式类型转换?


在MySQL中,当操作符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容,则会发生隐式类型转换。


即 MySQL会根据需要自动将数字转换为字符串,或者将字符串转换为数字。



mysql> SELECT 1+'1';
-> 2
mysql> SELECT CONCAT(2,' test');
-> '2 test'


很明显,上面的SQL语句的执行过程中就出现了隐式转化。


从结果我们可以判定,SQL1中将字符串的“1”转换为数字1,而在SQL2 中,将数字2转换为字符串“2”。


3.2 如何避免隐式类型转换?


3.2.1 清楚转换规则


只有当清楚的知道隐式类型转换的规则,才能从根本上避免产生隐式类型转换。

参考MySQL文档相关描述,确定隐式类型转换规则:

1、两个参数至少有一个是 NULL 时,比较的结果也是 NULL,例外是使用 <=> 对两个 NULL 做比较时会返回 1,这两种情况都不需要做类型转换
2、两个参数都是字符串,会按照字符串来比较,不做类型转换
3、两个参数都是整数,按照整数来比较,不做类型转换
4、十六进制的值和非数字做比较时,会被当做二进制串
5、有一个参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为 timestamp
6、有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较
7、所有其他情况下,两个参数都会被转换为浮点数再进行比较

验证示例:

mysql> SELECT 'aa' + 1;
-> '1'
mysql> show warnings;
+---------+------+----------------------------------------+
| Level   | Code | Message                                |
+---------+------+----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'aa' |
+---------+------+----------------------------------------+

上述示例中,将字符串 'aa' 和1进行求和,因为 'aa' 和数字1的类型不同,通过上述转换规则并且经查看warnings可以确认:隐式类型转化将字符串转为了 double 类型。


由于字符串是非数字型的,所以就会被转换为0,因此计算结果:0+1=1


3.2.2 使用内置函数显示转换


MySQL对数据进行类型转换,提供了cast() 和 convert()。


相同点:两者都是进行数据类型转换,实现的功能基本等同


不同点:两者的语法不同:cast(value as type) 、 convert(value,type)


将数值型转换为字符串型,应用示例如下:

mysql> SELECT CAST(123 as char);
-> '123'
mysql> SELECT CONVERT(123, char);
-> '123'


假如应用在开篇描述问题的查询中,则如下所示:


EXPLAIN SELECT * FROM users WHERE phone = CAST(123 AS CHAR);

执行计划所示:

微信图片_20220607211047.png


结果显示同应用字符串类型参数一样,可使用索引`idx_phone`。


3.2.3 类型保持一致


最简单的一种,保证查询应用规范,SQL参数类型与数据库中字段类型保持一致即可。


3.3 字符类型转换


另外,关于字符串类型转换的一些补充:

mysql> select '1a2b3c' = 1;
-> 1
mysql> select 'a1b2c3' = 0;
-> 1


从上面的例子可以得出:


  • 如果字符串的第一个字符就是非数字的字符,那么转换为数字就是0;

  • 如果字符串以数字开头,那转换的数字就是开头的那些数字对应的值,直到遇到非数字字符才结束。


4、总结


本文主要从问题入手,继而进行问题引申,最终挖掘出问题根源:MySQL隐式类型转换


同时也告诫我们日常在写SQL时一定要检查参数类型与数据库字段类型是否一致,否则可能造成隐式类型转换,不能正常应用索引,造成慢查询,甚至拖垮整个数据库服务集群。


如果参数不一致,也可以考虑使用CAST函数显性转换成一致类型。


数据表设计及应用绝非易事,需要考虑的因素太多了,大家应用过程注意保持敬畏心。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
关系型数据库 MySQL
MySQL - 类型转换函数:CONVERT & CAST
MySQL - 类型转换函数:CONVERT & CAST
110 0
|
关系型数据库 MySQL 安全
MySQL的隐式类型转换
官方解读: 1.如果一个或两个参数都是NULL,比较的结果是NULL,除了NULL安全的相等比较运算符。对于NULL NULL,结果为true。不需要转换 2.如果比较操作中的两个参数都是字符串,则将它们作为字符串进行比较。
1041 0
|
关系型数据库 MySQL SQL
|
关系型数据库 MySQL Linux
|
MySQL 关系型数据库 索引
小心MySQL的隐式类型转换陷阱
1. 隐式类型转换实例 今天生产库上突然出现MySQL线程数告警,IOPS很高,实例会话里面出现许多类似下面的sql:(修改了相关字段和值) SELECT f_col3_id,f_qq1_id FROM d_dbname.t_tb1 WHERE f_col1_id=1226391 and f_c
3119 0
|
关系型数据库 MySQL 数据库
MySQL类型转换注意事项
最近在做的一项工作是将之前使用MSSQLServer数据库的服务, 改写为支持MySQL数据库,SQL语句中涉及了一些类型转换的功能, 结果发现MySQL的类型转换函数“CONVERT”并没有提供很好的数据类型名称的兼容性, 参数顺序上也不一样! SQLServer中使用con...
789 0
|
18天前
|
存储 Oracle 关系型数据库
数据库传奇:MySQL创世之父的两千金My、Maria
《数据库传奇:MySQL创世之父的两千金My、Maria》介绍了MySQL的发展历程及其分支MariaDB。MySQL由Michael Widenius等人于1994年创建,现归Oracle所有,广泛应用于阿里巴巴、腾讯等企业。2009年,Widenius因担心Oracle收购影响MySQL的开源性,创建了MariaDB,提供额外功能和改进。维基百科、Google等已逐步替换为MariaDB,以确保更好的性能和社区支持。掌握MariaDB作为备用方案,对未来发展至关重要。
45 3