应使用 decimal 而非 float 或 double 存储金额,因其按十进制精确计算,避免二进制浮点误差;建表时需指定 not null 和 default 0.00,控制精度并显式转换以防隐式类型转换导致精度丢失。

用 DECIMAL 而不是 FLOAT 或 DOUBLE
金额必须精确存储,FLOAT 和 DOUBLE 会引入二进制浮点误差,比如 0.1 + 0.2 可能存成 0.30000000000000004,导致对账不一致。MySQL 的 DECIMAL(M,D) 是定点数类型,按十进制精确计算和存储。
-
M是总位数(精度),D是小数位数(标度);例如DECIMAL(10,2)最多存 99999999.99,适合大多数货币场景 - 不要设过大:比如
DECIMAL(20,6)看似“更安全”,但会浪费存储、拖慢索引性能,且多数支付系统只保留 2 位小数 - 注意:
DECIMAL在排序、聚合时默认按数值比较,但若字段允许 NULL,需额外处理空值逻辑
建表时显式指定 NOT NULL 和默认值
金额字段极少应为 NULL,NULL 容易在 SUM()、AVG() 中被忽略,或引发业务逻辑歧义。明确约束可减少后续排查成本。
- 用
NOT NULL强制非空,避免意外插入 NULL - 设默认值如
DEFAULT 0.00,比让应用层补 0 更可靠 - 示例建表语句:
CREATE TABLE orders ( id BIGINT PRIMARY KEY, amount DECIMAL(10,2) NOT NULL DEFAULT 0.00 );
应用层写入前做类型校验和舍入
数据库不负责业务级舍入规则(比如四舍五入还是截断),也不能阻止应用传入超长小数。必须在写入前控制精度。
- PHP 中用
round($value, 2, PHP_ROUND_HALF_UP);Python 用round(value, 2)(注意 Python 3.5+ 的 round 行为) - 避免直接拼接字符串进 SQL,尤其不要用
float类型变量构造金额——JavaScript 的Number就是 IEEE 754,必须先转成字符串再交给后端 - 前端提交金额时,限制输入框最多两位小数,并禁用粘贴(防止粘贴带多余小数位的数字)
查询时别依赖隐式转换
MySQL 在比较或计算中可能把 DECIMAL 转成 DOUBLE,尤其涉及函数如 ROUND()、AVG() 或跨字段运算时,精度可能悄悄丢失。
- 显式用
CAST(amount AS DECIMAL(10,2))确保中间结果仍为定点数 - 避免
WHERE amount = 99.9这类写法——99.9 是浮点字面量,MySQL 会先转成 DOUBLE 再比较;应写成WHERE amount = 99.90或WHERE amount = CAST(99.9 AS DECIMAL(10,2)) - 聚合函数如
SUM()对DECIMAL字段本身是安全的,但若参与子查询或 JOIN 后计算,需检查执行计划是否触发隐式转换
实际项目里最常出问题的不是建表那一刻,而是某次加了个新报表 SQL,用了 ROUND(amount * 1.1) 却没意识到 MySQL 默认按 DOUBLE 计算——结果千条订单差了几分钱,查起来非常隐蔽。











