MySQL“索引失效”的隐形杀手:隐式类型转换,你了解多少?

在前面的文章中我们写了一些会导致索引失效的场景,比如《MySQL中,IS NULL和IS NOT NULL不会走索引?错!》一文。

今天这篇文章再介绍另外一种非常隐蔽,但又很容易导致索引失效的场景:隐式类型转换。隐式类型转换使用不当,轻则导致索引失效,性能急剧下降,重则会导致SQL语句未查询到预期的结果。

什么是隐式类型转换?

MySQL中的隐式类型转换规则是在查询或操作(如比较、函数调用等)中,涉及到不同数据类型时发生的自动转换行为。

如果参与操作的表达式或列的数据类型不匹配,MySQL会根据数据类型的上下文自动进行数据类型转换以适配预期的数据类型。这种行为对性能和结果有时会有较大的影响,比如索引可能失效或比较结果出现意外。

隐式类型转换典型案例

在进一步介绍隐式类型转换的详细规则之前,我们先来看两个比较典型的案例,这里采用的MySQL 8.0.37版本。

场景一:未获得预期数据,且索引失效

创建&初始化示例表

表结构及数据如下:

sql 复制代码
-- tb_type_change
CREATE TABLE `tb_type_change` (
  `col1` varchar(255) NOT NULL DEFAULT '',
  `col2` int NOT NULL DEFAULT '0',
  KEY `idx_c1` (`col1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
​
-- 插入一些数据
insert into tb_type_change values('1234567890123456789',1);
insert into tb_type_change values('123456789012345678',2);
insert into tb_type_change values('123456789012345677',3);
insert into tb_type_change values('12345678901234567',4);
insert into tb_type_change values('12345678901234568',5);
insert into tb_type_change values('123456789012345',6);

查询所有数据:

sql 复制代码
mysql> select * from tb_type_change;
+---------------------+------+
| col1                | col2 |
+---------------------+------+
| 1234567890123456789 |    1 |
| 123456789012345678  |    2 |
| 123456789012345677  |    3 |
| 12345678901234567   |    4 |
| 12345678901234568   |    5 |
| 123456789012345     |    6 |
+---------------------+------+

未获得预期数据示例

执行如下SQL语句:

sql 复制代码
mysql> select * from tb_type_change where col1 = 123456789012345678;
+--------------------+------+
| col1               | col2 |
+--------------------+------+
| 123456789012345678 |    2 |
| 123456789012345677 |    3 |
+--------------------+------+

通过上面的查询可以看到,SQL语句正常执行,但查询的结果并不是预期的结果。查询条件是123456789012345678,但结构中竟然包含了"123456789012345677"。这就是因为隐式类型转换导致未获得预期数据。

原因分析

在 MySQL 中,当查询条件中的数据类型与列的数据类型不匹配时,会发生隐式类型转换。隐式类型转换通常会将查询条件类型转换为列的类型或反之。如果转换过程中出现精度丢失或者未精确匹配,就可能导致查询结果不符合预期。

在上述场景中,表 tb_type_change 中列 col1 的类型是 VARCHAR,但查询条件 123456789012345678 是一个数字(BIGINT 型)。MySQL 会尝试将 VARCHAR 列的数据转换为 BIGINT 类型以进行比较。

  • BIGINT 的范围是 -92233720368547758089223372036854775807,但在比较时,MySQL 会将col1的值从字符串转换为数字。
  • 转换过程中,如果 col1 的字符串值超过 BIGINT 的最大精度范围,MySQL 会截断或丢失部分精度,使得原始字符串被转换为近似的 BIGINT 值。

在上述示例中,关于类型的转换:

  • '123456789012345677' 转换为 123456789012345678(数字,伴随一定的舍入)。
  • '123456789012345678' 转换为 123456789012345678(数字)。

因此,上述SQL语句查询出了两个结果。

场景二:隐式类型转换,索引失效

创建&初始化示例表

新创建一个表以及插入一些数据:

sql 复制代码
CREATE TABLE test (
    col1 VARCHAR(255) NOT NULL DEFAULT '',
    col2 INT NOT NULL,
    KEY idx_col1 (col1),          -- 对字符串列 col1 建立了索引
    KEY idx_col2 (col2)           -- 对整数列 col2 建立了索引
);
​
INSERT INTO test (col1, col2) VALUES 
('123', 123),
('456', 456),
('789', 789),
('abc', 111),
('xyz', 222);

未走索引,全表扫描示例

执行如下SQL语句:

yaml 复制代码
mysql> EXPLAIN SELECT * FROM test WHERE col1 = 123 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: ALL
possible_keys: idx_col1
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 5
     filtered: 20.00
        Extra: Using where

会发现上面的SQL语句,并没有走idx_col1索引,而是进行了全表扫描。

正常使用索引示例

再执行另外一个SQL语句:

yaml 复制代码
mysql> EXPLAIN SELECT * FROM test WHERE col2 = '123' \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: ref
possible_keys: idx_col2
          key: idx_col2
      key_len: 4
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL

此时,针对col2列的查询,正确使用了idx_col2索引。

原因分析

在上面的示例中:

  • 当字段类型为字符串类型,参数为整型时,会导致索引失效;
  • 而字段类型为整型,传入的参数为字符串类型时,不会导致索引失效;

这是因为在字符串与数字进行比较时,MySQL会将字符串类型转换为数字进行比较,因此当字段类型为字符串时,会在字段上加函数,而导致索引失效。

MySQL隐式类型转换规则

下面我们整理一些在MySQL当中常见的隐式类型转换规则。

字符串与数字

当一个字符串与一个数字进行比较时,字符串会被转换为一个数字。转换是基于字符串的数值前缀。如果字符串没有数值前缀,则转换结果为 0。上面的案例中,便是字符串转数字的场景之一。

示例:

ini 复制代码
SELECT '6' = 6; -- 返回 1(TRUE),因为字符串 '6' 被转换为数字 6。
SELECT '6a' = 6; -- 返回 1(TRUE),因为字符串 '6a' 在转换时被认定为数字 6。

不同类型的数值

不同类型的数值(例如 INT 和 DOUBLE)在比较时会转换为精度更高的数值类型。

示例:

ini 复制代码
SELECT 5 = 5.0; -- 返回 1(TRUE),整型 5 转换为浮点数 5.0 进行比较。

数值与日期比较

日期格式的数据和整型比较时会将整型转化为日期格式,但是日期格式的字符串和整型比较会将日期字符串转化为整型。

sql 复制代码
SELECT CAST('20230101' as date)=20230101; -- 返回 1(TRUE)
SELECT DATE'2023-01-01' =20230101;  -- 返回 1(TRUE)
SELECT '2023-01-01'=20230101; -- 返回 0(FALSE)
SELECT '2023-01-01'=2023; -- 返回 1(TRUE)

其他规则

  • 两个参数至少有一个是NULL时,比较的结果也是 NULL,例外是使用<=>对两个NULL做比较时会返回 1,这两种情况都不需要做类型转换;
  • 两个参数都是字符串,会按照字符串来比较,不做类型转换;
  • 两个参数都是整数,按照整数来比较,不做类型转换;
  • 十六进制的值和非数字做比较时,会被当做二进制串;
  • 有一个参数是TIMESTAMPDATETIME,并且另外一个参数是常量,常量会被转换为TIMESTAMP
  • 有一个参数是decimal类型,如果另外一个参数是decimal或者整数,会将整数转换为decimal后进行比较,如果另外一个参数是浮点数,则会把decimal转换为浮点数进行比较;
  • 同一类型内部的转换。例如,比较TINYINTBIGINT时,TINYINT会被转换为BIGINT
  • 使用BLOBTEXT类型时,应尽量避免使用不同类型的字面值,因为这可能导致意外的类型转换或比较结果。

小结

隐式转换的类型主要有字段类型不一致、IN参数包含多个类型、字符集类型或校对规则不一致等。数据库在进行隐式转换时,如果转换无法正常进行或产生了错误的结果,可能会影响查询的准确性和性能。因此,在设计数据库和编写 SQL 查询时,需仔细甄别,最好显式指定所需的数据类型,以避免潜在的问题。

相关推荐
Logintern094 小时前
windows如何设置mongodb的副本集
数据库·windows·mongodb
XYiFfang5 小时前
【MYSQL】SQL学习指南:从常见错误到高级函数与正则表达式
sql·mysql·正则表达式·regexp_like·group_concat
叫我龙翔5 小时前
【MySQL】从零开始了解数据库开发 --- 数据表的约束
android·c++·mysql·数据库开发
RestCloud6 小时前
在制造业数字化转型浪潮中,数据已成为核心生产要素。然而,系统割裂、数据滞后、开发运维成本高等问题,却像顽固的 “数据枷锁”,阻碍着企业发展。ETLCloud与
数据库·postgresql
!chen6 小时前
【Spring Boot】自定义starter
java·数据库·spring boot
流烟默6 小时前
MySQL索引调优之索引顺序必须和字段顺序一致吗?
mysql·索引调优
十碗饭吃不饱6 小时前
sql报错:java.sql.SQLSyntaxErrorException: Unknown column ‘as0‘ in ‘where clause‘
java·数据库·sql
我是Superman丶7 小时前
【优化】Mysql指定索引查询或忽略某个索引
数据库·mysql
程序定小飞7 小时前
基于springboot的在线商城系统设计与开发
java·数据库·vue.js·spring boot·后端