MySQL性能优化(四)隐式类型转换

俊达3年前技术文章766

我们知道, where条件中,对一个索引字段进行运算会导致无法使用该字段的索引。

有些情况下,即使没有显式地对索引字段进行运算,但是数据库会进行隐式类型转换,这也会导致无法使用索引。


会发生隐式类型转换的常见情况包括:

1、字段类型为字符串,存储了数字,where条件中传入的参数为数字类型。

2、字段类型为字符串,存储了日期格式的信息,where条件中传入了日期类型

3、字段类型和传入的参数都是字符串类型,但是字符集不匹配。

4、两表关联时,关联字段的类型不一致,或字符集不一致。



一个例子

CREATE TABLE `tab` (
  `id` int NOT NULL AUTO_INCREMENT,
  `phone` varchar(13) DEFAULT NULL,
  `phone2` bigint DEFAULT NULL,
  `create_time` varchar(20) DEFAULT NULL,
  `create_time2` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_phone` (`phone`),
  KEY `idx_phone2` (`phone2`),
  KEY `idx_createtime` (`create_time`),
  KEY `idx_createtime2` (`create_time2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4


情况1: 索引字段类型为varchar,传入数字类型的参数

mysql> explain  select * from tab where phone=13512345678;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tab   | NULL       | ALL  | idx_phone     | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)


mysql> show warnings;
+---------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                                                                                                                                                                  |
+---------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use ref access on index 'idx_phone' due to type or collation conversion on field 'phone'                                                                                                                                                                          |
| Warning | 1739 | Cannot use range access on index 'idx_phone' due to type or collation conversion on field 'phone'                                                                                                                                                                        |
| Note    | 1003 | /* select#1 */ select `test`.`tab`.`id` AS `id`,`test`.`tab`.`phone` AS `phone`,`test`.`tab`.`phone2` AS `phone2`,`test`.`tab`.`create_time` AS `create_time`,`test`.`tab`.`create_time2` AS `create_time2` from `test`.`tab` where (`test`.`tab`.`phone` = 13512345678) |
+---------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)


将传入的参数改成字符串类型,就可以使用到索引:

mysql> explain  select * from tab where phone='13512345678';
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key       | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | tab   | NULL       | ref  | idx_phone     | idx_phone | 55      | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                    |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `test`.`tab`.`id` AS `id`,`test`.`tab`.`phone` AS `phone`,`test`.`tab`.`phone2` AS `phone2`,`test`.`tab`.`create_time` AS `create_time`,`test`.`tab`.`create_time2` AS `create_time2` from `test`.`tab` where (`test`.`tab`.`phone` = '13512345678') |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+


情况2: 索引字段类型为varchar,传入日期类型的参数

mysql> explain  select * from tab where create_time = date(now());
+----+-------------+-------+------------+------+----------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys  | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+----------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tab   | NULL       | ALL  | idx_createtime | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+-------+------------+------+----------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)

mysql> show warnings;
+---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                                                                                                                                                                                                                         |
+---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use ref access on index 'idx_createtime' due to type or collation conversion on field 'create_time'                                                                                                                                                                                      |
| Warning | 1739 | Cannot use range access on index 'idx_createtime' due to type or collation conversion on field 'create_time'                                                                                                                                                                                    |
| Note    | 1003 | /* select#1 */ select `test`.`tab`.`id` AS `id`,`test`.`tab`.`phone` AS `phone`,`test`.`tab`.`phone2` AS `phone2`,`test`.`tab`.`create_time` AS `create_time`,`test`.`tab`.`create_time2` AS `create_time2` from `test`.`tab` where (`test`.`tab`.`create_time` = <cache>(cast(now() as date))) |
+---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)



如果字段类型为数字,但是传入字符串类型的参数,是否也会导致查询用不上索引呢? 我们来测试一下。

mysql> explain  select * from tab where phone2 = '13512345678';
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | tab   | NULL       | ref  | idx_phone2    | idx_phone2 | 9       | const |    1 |   100.00 | Using index condition |
+----+-------------+-------+------------+------+---------------+------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> show warnings;
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                   |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `test`.`tab`.`id` AS `id`,`test`.`tab`.`phone` AS `phone`,`test`.`tab`.`phone2` AS `phone2`,`test`.`tab`.`create_time` AS `create_time`,`test`.`tab`.`create_time2` AS `create_time2` from `test`.`tab` where (`test`.`tab`.`phone2` = 13512345678) |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


phone为数字类型,SQL中传入字符串类型的参数,但是还是可以使用索引。因为这种情况下,类型转换发生在传入的参数上,所以不影响使用索引。


类似的,下面这种情况也不影响使用索引

mysql> explain  select * from tab where create_time2 = '2023-01-01';
+----+-------------+-------+------------+------+-----------------+-----------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys   | key             | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+-----------------+-----------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | tab   | NULL       | ref  | idx_createtime2 | idx_createtime2 | 6       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+-----------------+-----------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                            |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `test`.`tab`.`id` AS `id`,`test`.`tab`.`phone` AS `phone`,`test`.`tab`.`phone2` AS `phone2`,`test`.`tab`.`create_time` AS `create_time`,`test`.`tab`.`create_time2` AS `create_time2` from `test`.`tab` where (`test`.`tab`.`create_time2` = TIMESTAMP'2023-01-01 00:00:00') |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)


总结

隐式类型转换可能会导致查询无法使用索引。做好以下几点,可以避免出现这种情况:

1、我们在设计表结构时,需要选择精确的字段类型。不要用字符串类型存储日期和数字。

2、同一个业务字段,在不同的表里面字段类型要保持一致,字符集也要保持一致。

3、查询的SQL,传入的参数要和字段的类型保持一致。

相关文章

SQL Server优化入门系列(二)—— 等待事件

SQL Server优化入门系列(二)—— 等待事件

在上一篇文章中(SQL Server优化入门系列(一)——快速定位阻塞SQL),我们介绍了如何快速定位SQL Server中当前正在执行的SQL,以及被阻塞的SQL。这里,我们将介绍如何通过等待事件来...

InnoDB秘籍:MVCC机制与行锁的深度探索(1)

InnoDB秘籍:MVCC机制与行锁的深度探索(1)

前言事务的起源可以追溯到 6000 年以前,当时苏美尔人(Sumerians)就发明了事务处理和记录的方法。已知最早的记录是写在土块上的,上面写了皇家的税收、土地、谷物、牲畜、奴隶和黄金,明确地记下了...

Kafka监控

1.监控健康状态为了了解 Kafka 的运作状态和性能状况需要对 Kafka 进行监控和诊断,通过Kafka提供的监控工具和插件可以诊断出 Kafka 的异常、错误、瓶颈和故障等问题并及时采取对应的措...

MySQL运维实战之ProxySQL(9.3)使用ProxySQL实现读写分离

proxysql读写分离主要通过mysql_query_rules表中的规则来实现。下面是具体的配置步骤:hostgroup配置insert into mysql_servers&...

Spark 对接 Alluxio

Spark 对接 Alluxio

1、概览        Spark 1.1 或更高版本的 Spark可以通过其与 HDFS 兼容的接口直接访问 Alluxio 集群。 使用 Alluxio 作为数据访问层,Spark 应用程序可以透...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。