金额必须用DECIMAL,不能用FLOAT或DOUBLE,否则0.1+0.2≠0.3等浮点误差会直接导致账单错误、财务对账失败;DECIMAL(10,2)是电商价格默认起点,支持最大99999999.99元,覆盖绝大多数单笔订单与成本场景,仅占5字节,比DECIMAL(24,6)节省超一半存储且查询更快。

金额必须用 DECIMAL,不能用 FLOAT 或 DOUBLE —— 否则 0.1 + 0.2 ≠ 0.3 这类误差会直接进账单、对不上财务系统。
为什么 DECIMAL(10,2) 是电商价格的默认起点
它表示最多 10 位数字,其中小数占 2 位,整数最多 8 位(即 -99999999.99 到 99999999.99)。这个范围覆盖绝大多数国内电商单笔订单(含优惠叠加)、库存成本、运费等场景。存储开销仅 5 字节(MySQL 5.7+),比 DECIMAL(24,6) 少一半以上空间,索引更紧凑,ORDER BY 和 GROUP BY 更快。
常见误用:DECIMAL(10,2) 存汇率或跨境结算价,结果四舍五入丢精度;或者用 DECIMAL(5,2) 存万元级商品,插入时直接被截断成 999.99。
- 够用就别堆大参数:业务最大订单额若不到 1000 万,
DECIMAL(10,2)足够 - 跨币种/手续费分摊需更高精度:改用
DECIMAL(12,4),保留万分位 - 不要盲目套用“保险起见”:
DECIMAL(24,6)在 MySQL 中占 16 字节,且参与计算时中间结果精度易溢出(如乘法后自动缩容)
DECIMAL 运算中精度怎么“悄悄变”
SQL 对 DECIMAL 字段做加减乘除时,结果的精度和小数位数不是简单继承原字段,而是按规则推导。比如两个 DECIMAL(10,2) 相乘,结果是 DECIMAL(21,4)(精度 = 10+10+1,小数位 = 2+2);但如果超出数据库最大精度 38,就会强制截断,导致隐式精度损失。
典型现象:SELECT price * quantity AS total FROM orders 返回值小数位突然变成 0 或 1 位,不是代码问题,是运算后系统自动降精度了。
- 加减法结果的小数位取两者中较大的
D值(如DECIMAL(10,2)+DECIMAL(12,4)→ 小数位为 4) - 乘法结果小数位默认为
s1 + s2,但若总精度 >38,则小数位会被压缩(例如DECIMAL(20,10) * DECIMAL(20,10)精度爆到 41,小数位会从 20 降到 17) - 用
CAST(... AS DECIMAL(M,D))显式转回目标精度,比依赖隐式推导更可控
查询时遇到 “Truncated incorrect DECIMAL value” 错误
这通常不是数据本身超限,而是你在 WHERE 条件里混用了字符串或浮点字面量。例如:WHERE price = 19.99 看似没问题,但 19.99 在某些客户端或驱动里会被当成 FLOAT 解析,再与 DECIMAL 比较时触发隐式转换警告甚至报错。
另一个高频场景:前端传参是 JSON 数字(如 {"min_price": 99.9}),后端没做类型校验直接拼 SQL,99.9 被当 float 处理,入库或查询时精度已失。
- 写死数值时加引号:
WHERE price = '19.99',确保按字符串解析再转DECIMAL - 参数化查询必须绑定为
DECIMAL类型(如 JDBC 的setBigDecimal,不是setDouble) - 避免在
WHERE中对DECIMAL字段用函数包装,如WHERE ROUND(price, 2) = 19.99—— 会绕过索引,还可能因 ROUND 行为不一致引入误差
ORDER BY 和 GROUP BY 中 DECIMAL 排序不准?
不是不准,是你的字段定义带了 UNSIGNED 但实际存了负数(比如退款记为负价格),MySQL 会把负值当极大正数处理(补码解释异常),排序全乱。或者字段用了 CHAR 存数字字符串(如 '19.99'),ORDER BY 按字典序排,'100.00' 会排在 '20.00' 前面。
更隐蔽的问题:联合索引里 DECIMAL 字段放在后面,但前导字段选择性差,优化器可能放弃走索引,改用文件排序(Using filesort),此时如果数据量大,DECIMAL 的高精度比较开销会放大延迟。
- 确认建表语句没漏掉
UNSIGNED误用(价格类一般要允许负值:退款、调整、冲正) - 杜绝用字符串类型存金额,哪怕加了约束也不行
- 在
ORDER BY price DESC前加EXPLAIN看是否走了索引;必要时单独给 price 加索引
最常被忽略的一点:应用层拿到 DECIMAL 查询结果后,又用 parseFloat() 或 float() 转一次——这步就把精度彻底搞丢了。从 DB 读出的十进制值,应该原样透传给前端或下游系统,中间不落地为二进制浮点。

















