必须用 decimal(m,d) 替代 float 或 double,因 ieee 754 二进制浮点无法精确表示多数十进制小数,导致计算误差;decimal 需显式定义合理 m、d,运算全程保持 decimal 类型,迁移时须验证数据并同步应用层。

必须用 DECIMAL(M,D) 替代 FLOAT 或 DOUBLE,否则任何“四舍五入”“格式化”都只是掩盖误差,不是解决精度问题。
为什么 FLOAT 和 DOUBLE 在计算中必然出错
根本原因不是函数或写法问题,而是 IEEE 754 二进制浮点表示法本身无法精确存储多数十进制小数。比如:
-
0.1 + 0.2在 MySQL 中执行结果是0.30000000000000004,不是0.3 -
INSERT INTO t VALUES (99.99)存入FLOAT字段后,可能实际存为99.98999999999999 -
SUM()、AVG()、比较操作(如WHERE price = 19.99)都会因底层值不精确而不可靠
DECIMAL(M,D) 的定义必须显式且合理
不写 M 和 D 就等于默认 DECIMAL(10,0),这在金额、汇率等场景下直接失效。
-
M是总位数(含小数点前+后),最大 65;D是小数位数,最大 30,且D ≤ M - 金额字段推荐
DECIMAL(15,2):支持亿级金额(9999999999999.99),保留分单位 - 汇率/利率建议
DECIMAL(12,6)或DECIMAL(15,8),预留足够小数位避免除法截断 - 别用
DECIMAL(10,2)存订单总额后突然接入跨境业务——100000000.00会溢出报错或静默截断
运算时所有参与值都得是 DECIMAL 类型
MySQL 遇到混合类型会隐式转为 DOUBLE,中间一步失准,后面全白搭。
- 错误写法:
SELECT price / 3 FROM orders—— 常量3是整数,但除法会触发隐式转DOUBLE,结果类型变成近似值 - 正确写法:
SELECT price / CAST(3 AS DECIMAL(5,2)) FROM orders,或更稳妥地写成price / 3.00(带小数点的字面量会被识别为DECIMAL) -
SUM()结果类型由输入列决定:若列是DECIMAL(10,2),SUM()默认返回DECIMAL(12,2)(MySQL 8.0+);但若混入FLOAT字段,整个结果就掉进浮点坑 - 存储过程中变量也必须声明完整:
DECLARE total DECIMAL(15,4);,不能只写DECIMAL
从 FLOAT 迁移到 DECIMAL 的实操风险点
直接 ALTER TABLE ... MODIFY COLUMN 很容易翻车,尤其存量数据已有长小数或超界值。
- 先检查真实数据长度:
SELECT price, LENGTH(CAST(price AS CHAR)) FROM orders WHERE price IS NOT NULL ORDER BY LENGTH(CAST(price AS CHAR)) DESC LIMIT 5; - 执行前加
SHOW WARNINGS;,留意类似Out of range value for column 'price'的警告 - 原字段有索引?
MODIFY会重建索引,大表务必选低峰期,加ALGORITHM=INPLACE(MySQL 5.6+)可减少锁表时间 - 应用层 ORM 映射要同步改(如 Java 的
BigDecimal),JSON 序列化也要防科学计数法(如 Jackson 配置WRITE_NUMBERS_AS_STRINGS)
最常被忽略的一点:精度问题从来不在 SELECT 输出端,而在 INSERT 和中间计算环节。哪怕你最后用 ROUND(x, 2) 格式化,如果 x 本身是 FLOAT 算出来的,那个“0.000000000000004”就已经混进去了。











