应使用decimal替代float/double存储精确小数,因后者二进制浮点表示无法精确还原十进制小数(如12.34存为12.340000343322754),导致sum、where等操作失效;decimal(m,d)以定点十进制存储,需显式声明m和d,迁移时旧值会四舍五入。

INSERT时FLOAT/DOUBLE列自动截断或失真
直接往FLOAT或DOUBLE列执行INSERT,哪怕值看起来规整(如12.34),底层也可能存成12.340000343322754——这不是显示问题,而是二进制浮点表示本身无法精确还原十进制小数。MySQL不会报错,但后续SUM、WHERE value = 12.34都可能失效。
根本原因在于:0.1、0.01、0.001等常见小数在二进制中是无限循环小数,FLOAT(23位尾数)和DOUBLE(52位尾数)只能做近似截断存储。
- 别指望
FLOAT(10,2)能“保证两位小数精度”——它只是限制显示宽度,不改变底层二进制存储逻辑 -
INSERT INTO t(v) VALUES(12.34)中字面量12.34被MySQL按DOUBLE解析,再转存进FLOAT列,中间已损失一次精度 - 用
SELECT v, HEX(v) FROM t可查到实际存储的二进制编码,验证是否已漂移
用DECIMAL替代FLOAT/DOUBLE列定义
这是最直接有效的修复方式:DECIMAL(M,D)以定点十进制字符串形式存储,完全规避二进制浮点体系。但必须显式声明M(总位数)和D(小数位数),漏掉任一参数会报ERROR 1064。
示例操作:
ALTER TABLE orders MODIFY COLUMN amount DECIMAL(15,2);
-
DECIMAL(15,2)覆盖亿元级金额+分,金融场景首选 -
DECIMAL(12,6)适合汇率、利率等高精度小数 - 修改后需重新
INSERT数据——旧FLOAT值转入DECIMAL列时,MySQL会按当前值四舍五入(如12.345→12.35),不是无损迁移
INSERT SELECT中源/目标类型不匹配导致隐式转换
执行INSERT INTO t2 SELECT * FROM t1时,若t1.v是FLOAT而t2.v是DECIMAL(10,2),MySQL会把FLOAT值先转为DOUBLE再转DECIMAL,中间多一次浮点误差放大。
- 强制源头转定点:
INSERT INTO t2(v) SELECT CAST(v AS DECIMAL(10,2)) FROM t1; - 避免跨类型复制:先
CREATE TABLE t2 AS SELECT CAST(v AS DECIMAL(10,2)) v FROM t1 LIMIT 0;建表,再INSERT ... SELECT - 检查目标列定义:
SHOW COLUMNS FROM t2 LIKE 'v';确认类型已是DECIMAL而非FLOAT
INSERT时传字符串字面量比数字字面量更安全
当INSERT语句中直接写数值,如VALUES(12.34),MySQL默认按DOUBLE解析;但写成字符串VALUES('12.34'),MySQL会优先尝试转为DECIMAL(尤其目标列为DECIMAL时)。
- 对
DECIMAL列:INSERT INTO t(v) VALUES('12.34');比VALUES(12.34)更可靠 - 对
FLOAT列:字符串写法不能根治问题,只是减少一次隐式转换环节 - 应用层拼SQL时,金额类字段务必用字符串格式传参,不要用float/double变量直接拼接
真正容易被忽略的是:精度丢失往往发生在类型链断裂的瞬间——比如FLOAT列值参与JOIN或GROUP BY,MySQL内部会把它升格为DOUBLE再计算,结果再塞回DECIMAL变量时,误差已不可逆。一旦用了FLOAT或DOUBLE,就等于主动放弃了确定性。











