写点什么

关于 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 表结构

CREATE TABLE `test_pp` (  `r_qty` decimal(13,4) DEFAULT NULL  ... ...) ;
复制代码

1.4 报错日志

Data truncated for column ‘%s’ at row %d:


[2025/07/17 20:00:43.701 +08:00] [INFO] [conn.go:1160] ["command dispatched failed"] [conn=2631925850] [session_alias=] [connInfo="id:2631925850, addr:xx.xx.xx.xx:xxxxx status:10, collation:utf8mb4_0900_ai_ci, user:test"] [command=Execute] [status="inTxn:0, autocommit:1"] [sql="insert into test_pp(...] [txn_mode=PESSIMISTIC] [timestamp=0] [err="[types:1265]Data truncated for column '%s' at row %d\ninsert into test_pp(...\ngithub.com/pingcap/tidb/pkg/server.(*clientConn).executePreparedStmtAndWriteResult\n\t/workspace/source/tidb/pkg/server/conn_stmt.go:317\ngithub.com/pingcap/tidb/pkg/server.(*clientConn).executePlanCacheStmt\n\t/workspace/source/tidb/pkg/server/conn_stmt.go:234\ngithub.com/pingcap/tidb/pkg/server.(*clientConn).handleStmtExecute\n\t/workspace/source/tidb/pkg/server/conn_stmt.go:225\ngithub.com/pingcap/tidb/pkg/server.(*clientConn).dispatch\n\t/workspace/source/tidb/pkg/server/conn.go:1384\ngithub.com/pingcap/tidb/pkg/server.(*clientConn).Run\n\t/workspace/source/tidb/pkg/server/conn.go:1127\ngithub.com/pingcap/tidb/pkg/server.(*Server).onConn\n\t/workspace/source/tidb/pkg/server/server.go:740\nruntime.goexit\n\t/usr/local/go/src/runtime/asm_amd64.s:1650"]
复制代码

1.5 原始 SQL

以下 SQL 是复现报错时抓取的完整 SQL。以下 SQL 在 mysql 客户端,无论常规执行或 prepare 方式执行都不会报错;在 JAVA 代码中关闭 prepare 执行不报错,开启 prepare 执行报错,说明问题和 prepare 语句有关。


INSERT INTO test_pp ( r_qty )VALUES  (    0.0001258850097656250000000000000000000000000000000000000000000000000000001,    ... ....  )
复制代码


经过分析我们发现,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 路径下复现:


  1. 使用 decimal(M,D) 类型,M:范围是 1 到 65,D:范围是 0 到 M

  2. 只有在 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


  1. 该问题通过 mysql 客户端执行 prepare 语句无法复现,变量赋值会自动截断转化为合法的 decimal:


经测试 MySQL 8.0 不存在此问题,证实了 TiDB v8.5.3 在处理 decimal 精度超长时确实有问题。

三、绕过方法

临时方法是代码在处理高精度数据时,避免确保小数位数超 72 位,例如通过函数确保小数位不超长:


cast( 字段 as decimal(13, 4) );
复制代码

四、问题修复

反馈问题后,得到了快速响应,预计在 v8.5.4 版本修复。


发布于: 刚刚阅读数: 3
用户头像

TiDB 社区官网:https://tidb.net/ 2021-12-15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
关于 decimal 精度问题_8.x 实践_TiDB 社区干货传送门_InfoQ写作社区