MySQL隐式类型转换导致数据不一致案例

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介:

1.1 问题描述

  一条表关联SQL导致,执行效率较差。发现原因关联字段数据类型不一致导致隐式类型转换,索引失效。使用convert转换后发现查询结果发生改变。

表结构

###t1###

CREATE TABLE `t1` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int NOT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `test_json` json NOT NULL,
  `num_dec` decimal(10,0) NOT NULL,
  `num_dou` double NOT NULL,
  `num_flo` float NOT NULL,
  `test_test` text NOT NULL,
  `joinnum` bigint NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

###t2###

CREATE TABLE `t2` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int NOT NULL,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `stamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `test_json` json NOT NULL,
  `num_dec` decimal(10,0) NOT NULL,
  `num_dou` double NOT NULL,
  `num_flo` float NOT NULL,
  `test_test` text NOT NULL,
  `joinnum` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_joinnum` (`joinnum`)
) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

问题SQL

#该SQLa.joinnum数据类型为为bigint,b.joinnum数据类型为varchar。存在隐式类型转换,无法使用索引进行关联(因为本文使用8.0,所以走了hash join)。
root@mysql8 16:13:  [test]> select count(*) from t1 a join t2 b on a.joinnum=b.joinnum;
+----------+
| count(*) |
+----------+
|   249996 |
+----------+
1 row in set (0.67 sec)
root@mysql8 16:16:  [test]> explain select count(*) from t1 a join t2 b on a.joinnum=b.joinnum;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+-------+----------+---------------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows  | filtered | Extra                                                   |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+-------+----------+---------------------------------------------------------+
|  1 | SIMPLE      | a     | NULL       | ALL   | NULL          | NULL        | NULL    | NULL | 99605 |   100.00 | NULL                                                    |
|  1 | SIMPLE      | b     | NULL       | index | idx_joinnum   | idx_joinnum | 1022    | NULL | 99484 |    10.00 | Using where; Using index; Using join buffer (hash join) |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+-------+----------+---------------------------------------------------------+
2 rows in set, 3 warnings (0.00 sec)
root@mysql8 17:20:  [test]> show warnings;
+---------+------+-------------------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                               |
+---------+------+-------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use ref access on index 'idx_joinnum' due to type or collation conversion on field 'joinnum'   |
| Warning | 1739 | Cannot use range access on index 'idx_joinnum' due to type or collation conversion on field 'joinnum' |
+---------+------+-------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

改写SQL

# 将a.joinnum使用函数转为char类型,使其走索引。
root@mysql8 16:16:  [test]> select count(*) from t1 a join t2 b on convert(a.joinnum,char)=b.joinnum;
+----------+
| count(*) |
+----------+
|   100000 |
+----------+
1 row in set (0.37 sec)

root@mysql8 16:17:  [test]> explain select count(*) from t1 a join t2 b on convert(a.joinnum,char)=b.joinnum;
+----+-------------+-------+------------+------+---------------+-------------+---------+------+-------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref  | rows  | filtered | Extra                    |
+----+-------------+-------+------------+------+---------------+-------------+---------+------+-------+----------+--------------------------+
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL        | NULL    | NULL | 99605 |   100.00 | NULL                     |
|  1 | SIMPLE      | b     | NULL       | ref  | idx_joinnum   | idx_joinnum | 1022    | func |     1 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+------+---------------+-------------+---------+------+-------+----------+--------------------------+
2 rows in set, 1 warning (0.00 sec)

数据不一致实例SQL

#部分数据有数据不一致现象
root@mysql8 17:34:  [test]> select a.joinnum,b.joinnum from t1 a join t2 b on a.joinnum=b.joinnum limit 10;
+-------------------+-------------------+
| joinnum           | joinnum           |
+-------------------+-------------------+
| 10000000000000001 | 10000000000000001 |
| 10000000000000002 | 10000000000000002 |
| 10000000000000005 | 10000000000000003 |
| 10000000000000004 | 10000000000000003 |
| 10000000000000003 | 10000000000000003 |
| 10000000000000005 | 10000000000000004 |
| 10000000000000004 | 10000000000000004 |
| 10000000000000003 | 10000000000000004 |
| 10000000000000005 | 10000000000000005 |
| 10000000000000004 | 10000000000000005 |
+-------------------+-------------------+
10 rows in set (0.02 sec)

1.2 理论依据

https://dev.mysql.com/doc/refman/5.7/en/type-conversion.html

以下规则描述了比较操作如何发生转换:

If one or both arguments are NULL, the result of the comparison is NULL, except for the NULL-safe <=> equality comparison operator. For NULL <=> NULL, the result is true. No conversion is needed.
如果一个或两个参数均为NULL,则比较的结果为NULL,除了NULL-safe <=> 相等比较运算符外。对于NULL <=> NULL,结果为true。无需转换。
If both arguments in a comparison operation are strings, they are compared as strings.
如果比较操作中的两个参数都是字符串,则将它们作为字符串进行比较。
If both arguments are integers, they are compared as integers.
如果两个参数都是整数,则将它们作为整数进行比较。
Hexadecimal values are treated as binary strings if not compared to a number.
如果不与数字比较,则将十六进制值视为二进制字符串。
If one of the arguments is a TIMESTAMP or DATETIME column and the other argument is a constant, the constant is converted to a timestamp before the comparison is performed. This is done to be more ODBC-friendly. This is not done for the arguments to IN(). To be safe, always use complete datetime, date, or time strings when doing comparisons. For example, to achieve best results when using BETWEEN with date or time values, use CAST() to explicitly convert the values to the desired data type.
如果参数之一是a TIMESTAMP或 DATETIMEcolumn,而另一个参数是常量,则在执行比较之前,该常量将转换为时间戳。这样做是为了使ODBC更友好。对于的参数,此操作未完成 IN()。为了安全起见,在进行比较时,请始终使用完整的日期时间,日期或时间字符串。例如,为了在BETWEEN与日期或时间值一起使用时获得最佳结果 ,可使用CAST()将值显式转换为所需的数据类型。
A single-row subquery from a table or tables is not considered a constant. For example, if a subquery returns an integer to be compared to a DATETIME value, the comparison is done as two integers. The integer is not converted to a temporal value. To compare the operands as DATETIME values, use CAST() to explicitly convert the subquery value to DATETIME.
一个或多个表中的单行子查询不视为常量。例如,如果子查询返回要与DATETIME 值进行比较的整数,则比较将作为两个整数完成。整数不转换为时间值。要将操作数作为DATETIME值进行比较 ,请使用 CAST()将子查询值显式转换为DATETIME。
If one of the arguments is a decimal value, comparison depends on the other argument. The arguments are compared as decimal values if the other argument is a decimal or integer value, or as floating-point values if the other argument is a floating-point value.
如果参数之一是十进制值,则比较取决于另一个参数。如果另一个参数是十进制或整数值,则将参数作为十进制值进行比较;如果另一个参数是浮点值,则将参数作为浮点值进行比较。
In all other cases, the arguments are compared as floating-point (real) numbers. For example, a comparison of string and numeric operands takes place as a comparison of floating-point numbers.
在所有其他情况下,将参数作为浮点数(实数)进行比较。例如,将字符串和数字操作数进行比较,将其作为浮点数的比较。
  1. 避免发生隐式类型转换,隐式转换的类型主要有字段类型不一致、in参数包含多个类型、字符集类型或校对规则不一致等
  2. 隐式类型转换可能导致无法使用索引、查询结果不准确等,因此在使用时必须注意
  3. 数字类型的建议在字段定义时就定义为int或者bigint,表关联时关联字段必须保持类型、字符集、校对规则都一致
  4. 当字符串转换为浮点数时,数字位数至少在17位就会出现问题。

  使用浮点数有时会引起混淆,因为它们是近似值而不是作为精确值存储的。SQL语句中编写的浮点值可能与内部表示的值不同。尝试在比较中将浮点值视为精确值可能会导致问题。它们还受平台或实现依赖性的约束。该 FLOATDOUBLE数据类型都受到这些问题。对于DECIMAL列,MySQL执行的精度为65个十进制数字,这应该可以解决最常见的不准确性问题。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
20天前
|
canal 消息中间件 关系型数据库
Canal作为一款高效、可靠的数据同步工具,凭借其基于MySQL binlog的增量同步机制,在数据同步领域展现了强大的应用价值
【9月更文挑战第1天】Canal作为一款高效、可靠的数据同步工具,凭借其基于MySQL binlog的增量同步机制,在数据同步领域展现了强大的应用价值
116 4
|
28天前
|
关系型数据库 MySQL 数据库
RDS MySQL灾备服务协同解决方案构建问题之数据库备份数据的云上云下迁移如何解决
RDS MySQL灾备服务协同解决方案构建问题之数据库备份数据的云上云下迁移如何解决
|
18天前
|
监控 关系型数据库 MySQL
zabbix agent集成percona监控MySQL的插件实战案例
这篇文章是关于如何使用Percona监控插件集成Zabbix agent来监控MySQL的实战案例。
28 2
zabbix agent集成percona监控MySQL的插件实战案例
|
4天前
|
存储 关系型数据库 MySQL
技术解析:MySQL中取最新一条重复数据的方法
以上提供的两种方法都可以有效地从MySQL数据库中提取每个类别最新的重复数据。选择哪种方法取决于具体的使用场景和MySQL版本。子查询加分组的方法兼容性更好,适用于所有版本的MySQL;而窗口函数方法代码更简洁,执行效率可能更高,但需要MySQL 8.0及以上版本。在实际应用中,应根据数据量大小、查询性能需求以及MySQL版本等因素综合考虑,选择最合适的实现方案。
24 6
|
4天前
|
关系型数据库 MySQL 数据处理
针对MySQL亿级数据的高效插入策略与性能优化技巧
在处理MySQL亿级数据的高效插入和性能优化时,以上提到的策略和技巧可以显著提升数据处理速度,减少系统负担,并保持数据的稳定性和一致性。正确实施这些策略需要深入理解MySQL的工作原理和业务需求,以便做出最适合的配置调整。
27 6
|
23天前
|
SQL 存储 缓存
MySQL是如何保证数据不丢失的?
文章详细阐述了InnoDB存储引擎中Buffer Pool与DML操作的关系。在执行插入、更新或删除操作时,InnoDB为了减少磁盘I/O,会在Buffer Pool中缓存数据页进行操作,随后将更新后的“脏页”刷新至磁盘。为防止服务宕机导致数据丢失,InnoDB采用了日志先行(WAL)机制,通过将DML操作记录为Redo Log并异步刷新到磁盘,结合双写机制和合理的日志刷新策略,确保数据的持久性和一致性。尽管如此,仍需合理配置参数以平衡性能与数据安全性。
MySQL是如何保证数据不丢失的?
|
20天前
|
存储 关系型数据库 MySQL
|
20天前
|
SQL 关系型数据库 MySQL
SQL Server、MySQL、PostgreSQL:主流数据库SQL语法异同比较——深入探讨数据类型、分页查询、表创建与数据插入、函数和索引等关键语法差异,为跨数据库开发提供实用指导
【8月更文挑战第31天】SQL Server、MySQL和PostgreSQL是当今最流行的关系型数据库管理系统,均使用SQL作为查询语言,但在语法和功能实现上存在差异。本文将比较它们在数据类型、分页查询、创建和插入数据以及函数和索引等方面的异同,帮助开发者更好地理解和使用这些数据库。尽管它们共用SQL语言,但每个系统都有独特的语法规则,了解这些差异有助于提升开发效率和项目成功率。
92 0
|
3天前
|
NoSQL 关系型数据库 MySQL
微服务架构下的数据库选择:MySQL、PostgreSQL 还是 NoSQL?
在微服务架构中,数据库的选择至关重要。不同类型的数据库适用于不同的需求和场景。在本文章中,我们将深入探讨传统的关系型数据库(如 MySQL 和 PostgreSQL)与现代 NoSQL 数据库的优劣势,并分析在微服务架构下的最佳实践。
|
5天前
|
存储 SQL 关系型数据库
使用MySQL Workbench进行数据库备份
【9月更文挑战第13天】以下是使用MySQL Workbench进行数据库备份的步骤:启动软件后,通过“Database”菜单中的“管理连接”选项配置并选择要备份的数据库。随后,选择“数据导出”,确认导出的数据库及格式(推荐SQL格式),设置存储路径,点击“开始导出”。完成后,可在指定路径找到备份文件,建议定期备份并存储于安全位置。
65 11

热门文章

最新文章