关于 decimal 精度问题
作者: weiyinghua 原文来源:https://tidb.net/blog/222dce8d
#
一、问题说明
1.1 问题现象
当使用 JAVA 在 TiDB 执行超出 decimal 小数位范围的 prepare 语句时,会返回错误:Cause: java.sql.SQLException: Data truncated for column ‘%s’ at row %d:

把 SQL 手工改成 prepare 语句在 mysql 客户端执行不报错,问题无法复现,必须在应用程序中才能复现。
1.2 环境信息
JDK 版本:1.8.0_251
TiDB 版本:v8.5.2
1.3 表结构
1.4 报错日志
Data truncated for column ‘%s’ at row %d:
1.5 原始 SQL
以下 SQL 是复现报错时抓取的完整 SQL。以下 SQL 在 mysql 客户端,无论常规执行或 prepare 方式执行都不会报错;在 JAVA 代码中关闭 prepare 执行不报错,开启 prepare 执行报错,说明问题和 prepare 语句有关。
经过分析我们发现,decimal 类型字段小数位为 72 位时,JAVA 代码不会报错,比如:
0.000125885009765625000000000000000000000000000000000000000000000000000000
当 decimal 类型字段小数位超 72 位时,JAVA 代码立刻报错,说明问题和小数位长度有关:
0.0001258850097656250000000000000000000000000000000000000000000000000000001
二、问题根因
进一步分析发现,JDBC 配置了 SQL 以 prepare 方式执行,TiDB prepare 语句处理 decimal 小数位超长 72 位时,TiDB 内部未正确捕获异常,导致把 error 抛给应用程序引起报错,相关 issuse :https://github.com/pingcap/tidb/issues/62602 。非 prepare 路径处理 decimal 小数位超长时 TiDB 也会报错,但该报错会在 TiDB 内部正常捕获,所以该问题只会在 prepare 路径下复现:
使用 decimal(M,D) 类型,M:范围是 1 到 65,D:范围是 0 到 M
只有在 D > 72 时会返回报错 Date truncated,TiDB 代码逻辑是把整数和小数部分除以 9 向上取整,当取正后整数和小数加起来值超过 9 时,在当前有问题的 prepare 处理逻辑下会触发 Data truncated。例如:
0.0001258850097656250000000000000000000000000000000000000000000000000000001
小数位为 73 位,整数位只有 0,算作 1,小数位 Ceil(73 / 9) = 9,所以 1 + 9 > 9 触发 TiDB 产生 Data truncated … 报错。源码依据:https://github.com/pingcap/tidb/blob/d0ac8e61518286b3f1477c322a834385ee1e0227/pkg/types/mydecimal.go#L395
该问题通过 mysql 客户端执行 prepare 语句无法复现,变量赋值会自动截断转化为合法的 decimal:
经测试 MySQL 8.0 不存在此问题,证实了 TiDB v8.5.3 在处理 decimal 精度超长时确实有问题。
三、绕过方法
临时方法是代码在处理高精度数据时,避免确保小数位数超 72 位,例如通过函数确保小数位不超长:
四、问题修复
反馈问题后,得到了快速响应,预计在 v8.5.4 版本修复。
版权声明: 本文为 InfoQ 作者【TiDB 社区干货传送门】的原创文章。
原文链接:【http://xie.infoq.cn/article/c3f3aee873bc9e196516bf95d】。文章转载请联系作者。
评论