唯一能堵住精度丢失入口的做法是直接用decimal替换目标列类型;mysql在insert时对浮点数隐式转换不报错,仅按目标类型尽力而为,导致小数丢失、静默四舍五入或截断。

直接用 DECIMAL 替换目标列的类型,别在 INSERT SELECT 或裸值插入时依赖隐式转换——这是唯一能堵住精度丢失入口的做法。
为什么 INSERT 会悄悄丢小数?
MySQL 在 INSERT 过程中对浮点数做隐式类型转换时,不报错、不警告,只按目标列类型“尽力而为”:如果目标是 FLOAT 或 DOUBLE,0.1 实际存成 0.10000000149011611938;如果目标是窄 DECIMAL(5,2) 却插 123.456,它会静默四舍五入成 123.46,而不是拒绝写入。
常见错误现象:
-
INSERT INTO t2 SELECT * FROM t1,源表value FLOAT,目标表value FLOAT→ 小数位莫名变整数(如3.14 → 3) -
INSERT INTO t (amt) VALUES (0.1 + 0.2)→ 结果是0.30000000000000004而非0.3 - 目标列为
DECIMAL(8,2),但插入123456.789→ 自动截成99999.99(超出范围时)或四舍五入成123456.79(未超但标度不够)
改表结构:把 FLOAT/DOUBLE 列换成 DECIMAL
这是最彻底的解法。不要试图“修数据”,先修 schema。
- 确认当前列类型:
DESCRIBE table_name;或SHOW COLUMNS FROM table_name; - 修改为带精度和标度的
DECIMAL:ALTER TABLE table_name MODIFY COLUMN value DECIMAL(15,2); - 金额类场景推荐
DECIMAL(15,2)(覆盖亿元+分),汇率类可用DECIMAL(12,6) - 严禁只写
DECIMAL或DECIMAL(10)—— MySQL 会报ERROR 1064
INSERT 时强制走定点链:CAST 或带小数点字面量
即使列已是 DECIMAL,裸数字仍可能被当 DOUBLE 解析,中间一算就漂移。
- 安全写法:
INSERT INTO t (amt) VALUES (100.0), (0.08);(末尾加.0是最轻量提示) - 更稳妥写法:
INSERT INTO t (amt) VALUES (CAST(100 AS DECIMAL(10,2)), CAST(0.08 AS DECIMAL(5,4))); - 批量插入时别偷懒:
INSERT INTO t SELECT id, CAST(price AS DECIMAL(15,2)) FROM src; - 避免:
INSERT INTO t VALUES (100, 0.08);——0.08被当DOUBLE,乘法或除法后可能仍是DOUBLE类型
INSERT SELECT 场景下防隐式转换失效
当从一个表查出浮点值再插入另一个表,类型链极易断裂。
- 源列是
FLOAT?必须显式转:INSERT INTO t2 (id, amt) SELECT id, CAST(amt AS DECIMAL(15,2)) FROM t1; - 源列已是
DECIMAL,但精度不足?比如源是DECIMAL(10,2),目标要DECIMAL(15,2),可直接插;但如果目标是DECIMAL(8,2),需提前ROUND()或TRUNCATE(),否则静默截断 - 检查是否启用了宽松模式:
SELECT @@sql_mode;,若含ALLOW_INVALID_DATES等,隐式转换行为更难预测 - 临时规避(不推荐长期用):
INSERT INTO t2 SELECT id, ROUND(amt, 2) FROM t1;—— 仅修复显示层,底层仍是浮点误差
真正容易被忽略的是:一次没 CAST,整个计算链就变成 DOUBLE;而 MySQL 从不提醒你——它只是安静地返回一个“看起来对”的错值。











