【Mysql】字段隐式转换对where条件和join关联条件的影响

【Mysql】字段隐式转换对where条件和join关联条件的影响

【一】问题描述

数据库中用户表主键id是bigint类型,由雪花算法生成的id。另一个中间表的字段userId存的是varchar类型。使用MybatisPlus执行sql,传参是Long类型,出现以下两种情况:

(1)使用Long类型的参数userId=728666272023183375,从中间表里删除记录,会把userId=728666272023183374的记录也删除了

(2)使用用户表和中间表leftjoin的使用,用户表的id是bigint,中间表的userId是varchar,728666272023183375的用户会把728666272023183374的关联信息也查出来

根据上述情况分析,无论是where查询还是关联表join,都存在等号两边的字段类型不一致,出现字段类型隐式转换导致的精度丢失,导致查询错误

【二】问题分析

上述问题的核心原因是数据库隐式类型转换导致的数值匹配异常,具体如下:

【1】类型不匹配触发隐式转换(字符转数值)

用户表的id是bigint类型(数值型),中间表的userId是varchar类型(字符串型)。当用Long类型(对应数据库bigint)的参数与varchar字段比较时,数据库会触发隐式类型转换:将varchar类型的userId转换为数值型(而非将bigint转换为字符串)后再进行比较。

【2】大整数转换的精度丢失问题

MyBatis-Plus 雪花算法默认生成的 ID 是 19 位长整数,雪花算法生成的 ID 是 18-19 位的大整数(如728666272023183375)

MySQL 中用于存储大整数的主要类型是BIGINT,其精度存在硬性限制:

(1)BIGINT是 64 位有符号整数,取值范围为 -9223372036854775808 ~ 9223372036854775807(约 ±9e18)。

(2)这个范围对应的最大有效数字是18 位(9223372036854775807 是 19 位数字,但前 18 位是有效范围)。

数据库对数值类型的精度处理存在限制:

部分数据库(如 MySQL)对bigint的最大支持是 64 位有符号整数(范围约 ±9e18),但当varchar字段存储的字符串转换为数值时,若数值接近最大范围,可能因精度不足导致近似匹配(如728666272023183375和728666272023183374在转换后被误判为相等)。

字符串转数值时,若存在格式问题(如末尾有不可见字符),也可能导致转换后的数值与预期不符,进而匹配错误。

【3】具体场景解释

删除场景:执行DELETE FROM 中间表 WHERE userId = ?时,参数是Long类型(数值),数据库将userId(varchar)转为数值后比较。因精度丢失,728666272023183375和728666272023183374的转换结果被误判为相等,导致误删。

关联查询场景:LEFT JOIN的ON条件中,user.id(bigint)与中间表.userId(varchar)比较时,同样触发隐式转换,导致相近 ID 的关联信息被错误匹配。

【三】字段类型转换的原理

数据库处理不同类型字段的比较时,遵循隐式类型转换规则,核心逻辑如下:

【1】转换方向:字符串→数值(而非数值→字符串)

当数值类型(bigint/int)与字符串类型(varchar)比较时,数据库默认将字符串转换为数值后再比较,而非将数值转为字符串。原因是数值比较的优先级高于字符串比较(数据库认为数值语义更精确)。

【2】转换规则与风险

若字符串是纯数字(如"123"),转换为数值后可正常比较。

若字符串包含非数字字符(如"123a"),转换后可能为0或NULL(不同数据库行为不同)。

若字符串是超长数字(如 19 位以上),转换时可能因超出数据库数值类型的精度范围,导致截断、四舍五入或溢出,进而出现匹配错误(即本文问题的根源)。

【3】转换过程的细节:字符串→数值的溢出处理

MySQL 将字符串转换为数值时,遵循以下规则(以 19 位纯数字字符串为例):

(1)尝试将字符串解析为BIGINT类型(默认优先匹配最大的整数类型)。

(2)若字符串数值≤9223372036854775807(BIGINT最大值):可精确转换(18 位及以内的数值通常没问题)。

(3)若字符串数值>9223372036854775807:

MySQL 无法用BIGINT精确表示,会自动转为DOUBLE(双精度浮点数)处理。

但DOUBLE的精度有限(有效数字为 15-17 位),对于 19 位数值,超出 17 位的部分会被四舍五入或截断,导致精度丢失。

【4】示例:

字符型字段存储:"9223372036854775808"(19 位,超过BIGINT最大值)。

转换为DOUBLE后,可能被近似为9223372036854776000(后几位因精度不足被截断)。

此时若查询条件的数值参数是9223372036854775808,转换后与字段转换结果可能匹配,也可能因近似值冲突导致错误匹配(如与9223372036854775809的转换结果相同)。

【5】典型现象:相近数值的错误匹配

由于 19 位数值转换后精度丢失,会导致原本不同的数值在转换后被判定为相等,表现为:

用WHERE user_id = 728666272023183375(数值参数)查询时,可能同时匹配"728666272023183375"和"728666272023183374"(字符型字段值)。

原因是这两个 19 位数值转换为DOUBLE后,因精度限制被近似为同一个值(后几位差异被忽略)。

【四】解决方法

核心原则:避免隐式类型转换,确保比较双方类型一致。具体实现如下:

【1】方案1:传参时将Long转为String

在 Java 代码中,将Long类型的userId转换为String后再传入 SQL,确保参数与varchar字段类型一致,避免触发隐式转换。

java 复制代码
// 错误:传Long类型
Long userId = 728666272023183375L;
queryWrapper.eq("user_id", userId); // 触发隐式转换

// 正确:转为String后传参
String userIdStr = String.valueOf(userId);
queryWrapper.eq("user_id", userIdStr); // 字符串直接比较,无转换

【2】方案2:统一表字段类型

最彻底的方式是修改中间表的userId字段类型为bigint,与用户表的id类型保持一致,从根源上消除类型转换问题。

相关推荐
bing_1581 小时前
在多租户或多服务共享 Redis 时,如何做逻辑隔离或权限控制?
数据库·redis·缓存
ChaITSimpleLove1 小时前
PostgreSQL 中删除指定数据库下的所有表结构
数据库·postgresql·bash·sql 脚本·.net npgsql
生涯にわたる学び3 小时前
数据库02 网页html01 day44
数据库·html
Doris_LMS4 小时前
保姆级别IDEA关联数据库方式、在IDEA中进行数据库的可视化操作(包含图解过程)
java·mysql·postgresql
2301_793086874 小时前
Mysql group by
数据库·mysql
jllllyuz4 小时前
Spring中的事务是如何实现的
数据库·sql·spring
wangmengxxw5 小时前
Spring-常用注解
java·数据库·spring·注解
m0_747266095 小时前
contentprovider实验+SQLite数据库的实现
数据库·sqlite
_码农121385 小时前
spring boot + mybatis + mysql 只有一个实体类的demo
spring boot·mysql·mybatis