先给结论:金额字段优先使用精确表示
订单金额、手续费、税额、账户余额等字段,核心要求通常不是计算速度,而是结果可复现、可比较、可审计。对这类场景,MySQL 中应优先考虑 DECIMAL(M,D),而不是把金额直接放进 FLOAT 或 DOUBLE。
这不是说浮点类型不能做数学运算,而是它们表达的是近似值。MySQL 文档将 FLOAT 和 DOUBLE 归为近似数值类型,将 DECIMAL 归为定点类型;当业务需要精确的小数结果时,类型选择会直接影响存储、聚合和等值比较。
FLOAT、DOUBLE、DECIMAL 的差异
浮点类型:范围和性能优先
FLOAT、DOUBLE 使用浮点表示法。它们适合测量值、科学计算、统计模型或图形计算等允许误差的场景。十进制小数转换为二进制后,部分数值无法有限表示,因此写入和运算过程中可能发生舍入。
常见的直观例子是:
SELECT 0.1 + 0.2;不要把某个客户端显示出的结果当成所有版本和场景下的固定输出;真正需要关注的是,浮点运算不保证十进制金额的精确语义。即使单笔结果看起来正常,经过多次加减、聚合、分摊或跨系统传输后,最后几位也可能出现差异。
DECIMAL:固定精度和小数位
DECIMAL(M,D) 用 M 表示总位数、用 D 表示小数位数。例如 DECIMAL(12,2) 表示总共最多 12 位数字,其中 2 位在小数点后。它更适合需要精确十进制语义的金额和计费字段。
定义字段时不要只照搬一个规格:先估算业务允许的最大值、退款和累计汇总的范围,再为增长预留空间。D 也应按业务规则确定;人民币展示金额常见为两位小数,但积分、汇率、计量费用可能需要更多小数位。
可以用下面的表快速判断:
| 类型 | 表示方式 | 更适合的场景 | 金额字段建议 |
|---|---|---|---|
FLOAT |
浮点近似值 | 对精度要求较低的测量或计算 | 不建议 |
DOUBLE |
更高精度的浮点近似值 | 科学计算、统计和部分工程数据 | 仍不建议作为账务金额 |
DECIMAL |
定点十进制 | 金额、税额、费率和对账数据 | 优先考虑 |
不只要改数据库,应用层也要保持精确
数据库列使用 DECIMAL,并不意味着整条链路自动正确。应用代码如果先用二进制浮点计算,再把结果写入数据库,误差可能在入库前就已经产生。
Java 金额计算应使用 BigDecimal,并优先从字符串构造:
// 不要让 double 先参与构造
new BigDecimal(0.1);
// 从十进制文本构造精确值
new BigDecimal("0.1");Python 可以使用 Decimal;JavaScript 等环境则可以使用“以最小货币单位存储整数”的方式,或选择经过审查的十进制金额库。无论采用哪种方案,都应统一舍入模式、税费规则和分摊尾差处理,并在接口边界明确金额的单位与精度。
尤其要注意除法和分摊:中间步骤过早舍入,可能造成各明细之和与总额不一致。通常应保留足够的中间精度,在业务规定的最后一步统一舍入,并把剩余尾差按明确规则分配。
存量表从浮点改成 DECIMAL,先处理数据语义
小表可以在维护窗口执行类似变更:
ALTER TABLE orders
MODIFY amount DECIMAL(12,2) NOT NULL;但大表不能只看这条 SQL 是否能执行。修改列类型可能触发表重建、增加磁盘和临时空间消耗,并带来锁等待或复制延迟。生产环境应先在同版本、同规模的副本上评估,再根据 MySQL 版本、表大小和业务写入压力选择 Online DDL 或经过验证的在线变更工具。
迁移前建议按以下顺序检查:
- 确认字段语义:哪些列是真正的金额,哪些只是比例、测量值或中间计算结果。
- 确定范围和小数位:检查历史最大值、负数、空值、异常精度和未来汇总范围。
- 盘点依赖关系:核对索引、视图、报表、ETL、ORM 映射和下游接口。
- 定义历史数据处理规则:类型转换只能改变表示方式,无法凭空恢复已经丢失的业务精度;必要时结合订单明细、支付流水或审计记录重新核算。
- 分阶段验证:先在副本回放变更,再比较行数、总额、分组汇总和对账差异,最后安排灰度切换。
如果使用影子表或在线迁移工具,还要确认增量同步、主键、触发器、外键和切换回滚方案。不要把“列类型改成功”当成“账务数据已经正确”。
一套可落地的选型检查清单
- 需要精确十进制结果的字段,优先使用
DECIMAL。 M按最大业务值、累计值和增长空间估算,D按实际业务精度确定。- 应用层避免用
double或float作为金额计算的核心类型。 - 统一舍入、分摊和尾差规则,并补充边界测试。
- 对金额的加总、等值比较、跨服务序列化和数据库迁移建立自动化校验。
- 存量改造先做数据盘点和副本演练,再决定停机 DDL 还是在线迁移。
浮点类型并非“错误类型”,只是它解决的是近似数值问题。把数据类型和业务语义对应起来,金额字段的许多对账问题就能在建表阶段避免,而不是等到线上出现差额后再追查。