前言
在开发涉及金钱的系统时,比如电商、支付、财务等,我们几乎每天都要和"金额"打交道。
在对接一些银行系统时,金额通常使用的是整数,这一点让我产生了疑惑!
作为开发者,首先面临的一个问题就是:MySQL里应该用什么数据类型来保存金额? 常见的选择有两个:
decimal和bigint。今天我们就来掰扯掰扯,这两者到适用于什么场景?
一、先认识一下两位选手
1. DECIMAL:为精确小数而生
DECIMAL 是 MySQL 中专门用于存储精确小数的类型。你可以指定总位数和小数位数,比如 DECIMAL(10,2) 表示一共 10 位数字,其中小数部分占 2 位,整数部分 8 位,范围是 -99999999.99 到 99999999.99。
特点 :存储的是精确值,不会像 FLOAT/DOUBLE 那样出现浮点误差。非常适合金额这种需要精确计算的场景。
sql
CREATE TABLE orders (
id INT PRIMARY KEY,
amount DECIMAL(10,2)
);
-- 最多存 99999999.99 元
INSERT INTO orders VALUES (1, 12345.67);
2. BIGINT:用整数来"曲线救国"
BIGINT 是 64 位整数,范围 -2^63 到 2^63-1(约 -9.22×10^18 到 9.22×10^18)。
如果用它存金额,通常需要约定一个最小单位,比如"分"(1 元 = 100 分)。这样,金额就变成了整数存入数据库,显示时再除以相应的倍数。
特点:整数运算极快,且没有精度丢失(因为就是整数),但需要应用层做单位转换。
sql
CREATE TABLE orders (
id INT PRIMARY KEY,
amount_in_cents BIGINT
);
-- 以"分"为单位,12345.67 元存为 1234567
INSERT INTO orders VALUES (1, 1234567);
二、正面 PK:DECIMAL vs BIGINT
我们从几个关键维度来对比一下,看看谁更适合存钱。
1. 精度
- DECIMAL:天生就是精确小数,存多少就是多少,不会多一位也不会少一位。
- BIGINT:只要单位约定好(比如分),也是精确的。但要注意,如果业务需要更小的单位(比如厘),就要调整单位倍数,整数照样精确。
结论:两者都能保证精度,平手。
2. 存储空间
- DECIMAL :存储空间取决于定义的位数。MySQL 将每 9 位数字打包成 4 个字节(例如
DECIMAL(10,2)整数部分 8 位 + 小数部分 2 位 ≈ 10 位,占用 4 字节(前 9 位)+ 1 字节(剩余 1 位)= 5 字节左右)。位数越多,占用越大。DECIMAL(M,D)占用的字节数随M(总位数)变化;当M ≤ 18时,DECIMAL通常 ≤ 8 字节 ,当M ≥ 19时通常 > 8 字节。
- BIGINT:固定 8 字节,无论数值大小。
MySQL 中 DECIMAL 的存储规则(简化说明)
MySQL 把十进制数字按 每 9 位用 4 字节 打包,剩余不满 9 位的那部分按下表占用字节数:
剩余位数 r(1..9) → 占用字节
- 1--2 位:1 字节
- 3--4 位:2 字节
- 5--6 位:3 字节
- 7--9 位:4 字节
总字节数 =
floor(M / 9) * 4 + leftover_bytes(r),其中r = M % 9(若 r=0,则 leftover_bytes=0)。举例说明
BIGINT= 8 字节(固定)。DECIMAL(10,2):M=10 → groups=floor(10/9)=1 → leftover r=1 → bytes = 1*4 + 1 = 5 字节(比 BIGINT 小)。DECIMAL(17,2):M=17 → groups=1, r=8 → leftover_bytes=4 → bytes = 4 + 4 = 8 字节(等于 BIGINT)。DECIMAL(18,2):M=18 → groups=2, r=0 → bytes = 2*4 = 8 字节(等于 BIGINT)。DECIMAL(19,2):M=19 → groups=2, r=1 → bytes = 8 + 1 = 9 字节(比 BIGINT 大)。DECIMAL(20,2):M=20 → groups=2, r=2 → bytes = 8 + 1 = 9 字节(比 BIGINT 大)。
结论 :阈值大致在 M = 18/19 :当 M ≤ 18 时,DECIMAL 的存储通常不会超过 BIGINT 的 8 字节;当 M ≥ 19 时,DECIMAL 会占用更多空间。
3. 性能
- DECIMAL:CPU 对小数运算需要额外处理,尤其是做 SUM、AVG 等聚合时,比整数运算稍慢。
- BIGINT:整数运算是 CPU 的强项,速度飞快。在千万级数据量的报表统计中,差距会很明显。
结论 :BIGINT 性能更优,尤其适合需要大量计算的场景。
4. 运算方便性
- DECIMAL:可以直接在 SQL 里做加减乘除,结果也是精确小数,非常直观。
sql
-- 直接算税率
SELECT amount * 0.01 AS tax FROM orders;
- BIGINT :SQL 里做运算要注意单位。比如要算两个金额的平均值,结果仍是整数分,如果想得到元,还得手动除以 100,而且除法可能产生小数,需要配合
ROUND等函数。
sql
-- 这样会得到浮点数,可能不精确
SELECT (amount_in_cents * 0.01) FROM orders;
-- 更好的做法:先转换为小数,或者用整数运算再处理单位
结论 :DECIMAL 在 SQL 中更方便直观;BIGINT 则需要在应用层多一道转换,稍显麻烦。
5. 可读性与维护性
- DECIMAL:数据库里直接看到的就是"元.角分",DBA 或运维查询时一目了然。
- BIGINT:看到的是一串整数,得知道单位才能看懂。如果不小心搞混了单位(比如把分当成元),数据就全错了。
结论 :DECIMAL 更直观,降低沟通成本。
6. 范围
- DECIMAL :最大可以定义
DECIMAL(65,30),几乎无限大,适合超大金额。 - **BIGINT`:最大约 9.22×10^18,如果以分为单位,大约可存 9.22×10^16 元,也就是 9 万万亿,远超任何实际业务需求。以厘为单位也够用。
结论:两者范围都足够,除非是天文数字,否则无压力。
三、结合实例看选择
场景 1:普通电商订单
订单金额一般在几元到几十万元之间,需要两位小数(分)。用 DECIMAL(10,2) 非常直观,开发人员写代码时直接映射到 Java 的 BigDecimal,不会出错。
SQL 统计也很方便:
sql
SELECT SUM(amount) FROM orders WHERE create_date = '2023-01-01';
结果直接就是总金额(元),不需要额外处理。
此时推荐 DECIMAL。
场景 2:金融级系统,需要高精度
比如股票交易,可能涉及到"厘"(0.001 元)甚至更小的单位。如果用 DECIMAL,可以定义 DECIMAL(20,4) 等。但如果数据量巨大(每天上亿笔交易),统计报表时 DECIMAL 的聚合可能成为瓶颈。
此时可以用 BIGINT 以"毫"或"厘"为单位存储,例如 1 元 = 1000 厘。这样所有运算都是整数,性能极高。
sql
-- 以毫为单位存储 -- 12345.678 元
INSERT INTO trades (amount_in_mill) VALUES (12345678);
-- 统计时,应用层拿到整数除以 1000 得到元
但注意,除法会产生小数,需要在应用层用 BigDecimal 处理,避免精度丢失。
BIGINT 更适合高性能、海量数据的场景。
场景 3:分库分表或分布式系统
当数据量极大,需要分库分表时,BIGINT 作为整数更容易做分片键,且跨库聚合时整数运算更快。
此时可以考虑 BIGINT。
四、千万不要用 FLOAT 或 DOUBLE!
很多人新手会用 FLOAT 或 DOUBLE 存金额,结果踩坑。因为浮点数在计算机中无法精确表示十进制小数,比如 0.1 在二进制中是无限循环,存进去再取出来可能变成 0.099999994。
做金融计算时,分毫之差都会导致对账不平。所以 绝对不要用浮点数存金额。
五、到底怎么选?一张表帮你决定
| 因素 | DECIMAL | BIGINT(以最小单位存) |
|---|---|---|
| 精度 | 精确小数,无误差 | 精确整数,需约定单位 |
| 存储空间 | 数字较大时占用的空间大,否则反之 | 固定 8 字节 |
| 性能(运算) | 稍慢 | 极快 |
| SQL 开发便利性 | 直接,直观 | 需要单位转换,易出错 |
| 应用层处理 | 简单(如 Java 用 BigDecimal) | 需要手动转换单位 |
| 推荐场景 | 大部分业务系统,对性能不极端敏感 | 高性能、海量数据、需要整数运算的场景 |
我的建议:
- 80% 的情况用 DECIMAL :开发简单,维护容易,不容易出错。配合
BigDecimal类,完美匹配。 - 20% 的情况用 BIGINT:当你确定性能是关键瓶颈,或者系统已经足够成熟,有统一的单位约定和转换工具时,可以考虑。比如一些大型金融系统、广告计费系统等。
六、总结
金额存储没有绝对的"银弹",DECIMAL 和 BIGINT 都能胜任,关键看你的业务场景。
如果是普通项目,选 DECIMAL 省心;如果是追求极致性能的大型系统,选 BIGINT 并做好单位封装。
无论选哪种,记得永远不要用 FLOAT/DOUBLE,这是原则问题。