MySQL 中的 DOUBLE 与 DECIMAL

发布于 2024-11-26 13:14:29 字数 811 浏览 0 评论 0原文

好的,所以我知道有大量文章指出我不应该使用 DOUBLE 在 MySQL 数据库上存储资金,否则我最终会遇到棘手的精度错误。重点是我不是在设计一个新的数据库,而是要求我找到优化现有系统的方法。新版本包含 783 个 DOUBLE 类型列,其中大部分用于存储金钱或计算金额的公式。

所以我对这个主题的第一个意见是我应该强烈建议在下一个版本中从 DOUBLE 转换为 DECIMAL,因为 MySQL 文档和每个人都这么说。但后来我找不到任何好的论据来证明这一建议的合理性,原因有以下三个:

  • 我们不对数据库执行任何计算。所有操作均在 Java 中使用 BigDecimal 完成,MySQL 仅用作结果的普通存储。
  • DOUBLE 提供的 15 位精度已经足够了,因为我们主要存储 2 位小数的金额,偶尔也存储 8 位小数的小数字作为公式参数。
  • 我们在生产中拥有 6 年的记录,没有因 MySQL 端精度损失而出现任何已知错误问题。

即使在 1800 万行的表上执行操作(例如 SUM 和复杂乘法),我也无法执行缺乏精度的错误。我们实际上并没有在生产中做这类事情。我可以通过执行类似

SELECT columnName * 1.000000000000000 FROM tableName;

操作来显示精度损失,但我无法找到一种方法将其变成小数点后第二位的错误。我在互联网上发现的大多数实际问题都是 2005 年及更早版本的论坛条目,我无法在 5.0.51 MySQL 服务器上重现它们中的任何一个。

因此,只要我们不执行任何 SQL 算术操作(我们不打算这样做),仅在 DOUBLE 列中存储和检索金额是否会出现任何问题?

OK, so I know there are tons of articles stating I shouldn't use DOUBLE to store money on a MySQL database, or I'll end up with tricky precision bugs. The point is I am not designing a new database, I am ask to find way to optimise an existing system. The newer version contains 783 DOUBLE typed columns, most of them used to store money or formula to compute money amount.

So my first opinion on the subject was I should highly recommend a conversion from DOUBLE to DECIMAL in the next version, because the MySQL doc and everybody say so. But then I couldn't find any good argument to justify this recommandation, for three reasons :

  • We do not perform any calculation on the database. All operations are done in Java using BigDecimal, and MySQL is just used as a plain storage for results.
  • The 15 digits precision a DOUBLE offers is plenty enough since we store mainly amounts with 2 decimal digits, and occasionaly small numbers wit 8 decimal digits for formula arguments.
  • We have a 6 years record in production with no known issue of bug due to a loss of precision on the MySQL side.

Even by performing operations on a 18 millons rows table, like SUM and complex multiplications, I couldn't perform a bug of lack of precision. And we don't actually do this sort of things in production. I can show the precision lost by doing something like

SELECT columnName * 1.000000000000000 FROM tableName;

But I can't figure out a way to turn it into a bug at the 2nd decimal digit. Most of the real issues I found on the internet are 2005 and older forum entries, and I couldn't reproduce any of them on a 5.0.51 MySQL server.

So as long as we do not perform any SQL arithmetic operations, which we do not plan to do, are there any issue we should expect from only storing and retreiving a money amount in a DOUBLE column ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

最初的梦 2024-12-03 13:14:29

实际上是完全不同的。 DOUBLE 会导致舍入问题。如果你执行类似 0.1 + 0.2 的操作,它会给你类似 0.30000000000000004 的结果。我个人不相信使用浮点数学的财务数据。影响可能很小,但谁知道呢。我宁愿拥有我所知道的可靠数据,而不是近似数据,尤其是在处理货币价值时。

Actually it's quite different. DOUBLE causes rounding issues. And if you do something like 0.1 + 0.2 it gives you something like 0.30000000000000004. I personally would not trust financial data that uses floating point math. The impact may be small, but who knows. I would rather have what I know is reliable data than data that were approximated, especially when you are dealing with money values.

抹茶夏天i‖ 2024-12-03 13:14:29

MySQL 文档中的示例 http://dev.mysql。 com/doc/refman/5.1/en/problems-with-float.html (我缩小了它,本节的文档与 5.5 相同)

mysql> create table t1 (i int, d1 double, d2 double);

mysql> insert into t1 values (2, 0.00  , 0.00),
                             (2, -13.20, 0.00),
                             (2, 59.60 , 46.40),
                             (2, 30.40 , 30.40);

mysql> select
         i,
         sum(d1) as a,
         sum(d2) as b
       from
         t1
       group by
         i
       having a <> b; -- a != b

+------+-------------------+------+
| i    | a                 | b    |
+------+-------------------+------+
|    2 | 76.80000000000001 | 76.8 |
+------+-------------------+------+
1 row in set (0.00 sec)

基本上,如果你总结一下你会得到0-13.2+59.6+30.4 = 76.8。如果我们对b求和,我们得到0+0+46.4+30.4=76.8。 a 和 b 的总和是相同的,但 MySQL 文档说:

SQL 语句中写入的浮点值可能与内部表示的值不同。

如果我们用十进制重复相同的操作:

mysql> create table t2 (i int, d1 decimal(60,30), d2 decimal(60,30));
Query OK, 0 rows  affected (0.09 sec)

mysql> insert into t2 values (2, 0.00  , 0.00),
                             (2, -13.20, 0.00),
                             (2, 59.60 , 46.40),
                             (2, 30.40 , 30.40);
Query OK, 4 rows affected (0.07 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select
         i,
         sum(d1) as a,
         sum(d2) as b
       from
         t2
       group by
         i
       having a <> b;

Empty set (0.00 sec)

预期的结果是空集。

因此,只要您不执行任何 SQL 算术运算,您就可以使用 DOUBLE,但我仍然更喜欢 DECIMAL。

关于 DECIMAL 需要注意的另一件事是,如果小数部分太大,则进行舍入。例子:

mysql> create table t3 (d decimal(5,2));
Query OK, 0 rows affected (0.07 sec)

mysql> insert into t3 (d) values(34.432);
Query OK, 1 row affected, 1 warning (0.10 sec)

mysql> show warnings;
+-------+------+----------------------------------------+
| Level | Code | Message                                |
+-------+------+----------------------------------------+
| Note  | 1265 | Data truncated for column 'd' at row 1 |
+-------+------+----------------------------------------+
1 row in set (0.00 sec)

mysql> select * from t3;
+-------+
| d     |
+-------+
| 34.43 |
+-------+
1 row in set (0.00 sec)

The example from MySQL documentation http://dev.mysql.com/doc/refman/5.1/en/problems-with-float.html (i shrink it, documentation for this section is the same for 5.5)

mysql> create table t1 (i int, d1 double, d2 double);

mysql> insert into t1 values (2, 0.00  , 0.00),
                             (2, -13.20, 0.00),
                             (2, 59.60 , 46.40),
                             (2, 30.40 , 30.40);

mysql> select
         i,
         sum(d1) as a,
         sum(d2) as b
       from
         t1
       group by
         i
       having a <> b; -- a != b

+------+-------------------+------+
| i    | a                 | b    |
+------+-------------------+------+
|    2 | 76.80000000000001 | 76.8 |
+------+-------------------+------+
1 row in set (0.00 sec)

Basically if you sum a you get 0-13.2+59.6+30.4 = 76.8. If we sum up b we get 0+0+46.4+30.4=76.8. The sum of a and b is the same but MySQL documentation says:

A floating-point value as written in an SQL statement may not be the same as the value represented internally.

If we repeat the same with decimal:

mysql> create table t2 (i int, d1 decimal(60,30), d2 decimal(60,30));
Query OK, 0 rows  affected (0.09 sec)

mysql> insert into t2 values (2, 0.00  , 0.00),
                             (2, -13.20, 0.00),
                             (2, 59.60 , 46.40),
                             (2, 30.40 , 30.40);
Query OK, 4 rows affected (0.07 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select
         i,
         sum(d1) as a,
         sum(d2) as b
       from
         t2
       group by
         i
       having a <> b;

Empty set (0.00 sec)

The result as expected is empty set.

So as long you do not perform any SQL arithemetic operations you can use DOUBLE, but I would still prefer DECIMAL.

Another thing to note about DECIMAL is rounding if fractional part is too large. Example:

mysql> create table t3 (d decimal(5,2));
Query OK, 0 rows affected (0.07 sec)

mysql> insert into t3 (d) values(34.432);
Query OK, 1 row affected, 1 warning (0.10 sec)

mysql> show warnings;
+-------+------+----------------------------------------+
| Level | Code | Message                                |
+-------+------+----------------------------------------+
| Note  | 1265 | Data truncated for column 'd' at row 1 |
+-------+------+----------------------------------------+
1 row in set (0.00 sec)

mysql> select * from t3;
+-------+
| d     |
+-------+
| 34.43 |
+-------+
1 row in set (0.00 sec)
独守阴晴ぅ圆缺 2024-12-03 13:14:29

我们刚刚经历过同样的问题,但情况恰恰相反。也就是说,我们将美元金额存储为 DECIMAL,但现在我们发现,例如,MySQL 正在计算值 4.389999999993,但是当将其存储到 DECIMAL 字段时,它会将其存储为 4.38,而不是我们想要的 4.39它到。因此,虽然 DOUBLE 可能会导致舍入问题,但 DECIMAL 似乎也会导致一些截断问题。

We have just been going through this same issue, but the other way around. That is, we store dollar amounts as DECIMAL, but now we're finding that, for example, MySQL was calculating a value of 4.389999999993, but when storing this into the DECIMAL field, it was storing it as 4.38 instead of 4.39 like we wanted it to. So, though DOUBLE may cause rounding issues, it seems that DECIMAL can cause some truncating issues as well.

甜柠檬 2024-12-03 13:14:29

“仅在 DOUBLE 列中存储和检索金额是否会出现任何问题?”

听起来您的场景中不会产生舍入误差,如果有的话,它们会因转换为 BigDecimal 而被截断。

所以我会说不。

但是,不能保证将来的某些更改不会带来问题。

"are there any issue we should expect from only storing and retreiving a money amount in a DOUBLE column ?"

It sounds like no rounding errors can be produced in your scenario and if there were, they would be truncated by the conversion to BigDecimal.

So I would say no.

However, there is no guarantee that some change in the future will not introduce a problem.

入怼 2024-12-03 13:14:29

从你的评论来看,

税额四舍五入到小数点后第四位,总价四舍五入
精确到小数点后第二位。

使用评论中的示例,我可能会预见到 400 件商品的销售额为 1.47 美元的情况。税前销售额为 588.00 美元,税后销售额为 636.51 美元(税费为 48.51 美元)。然而,0.121275 美元* 400 的销售税将是48.52 美元。

这是一种强制实现一便士差价的方法,尽管是人为的。

我要指出的是,美国国税局 (IRS) 提供工资税表格,他们并不关心错误是否低于一定金额(如果没记错的话,0.50 美元)。

您的大问题是:有人关心某些报告是否相差一分钱吗?如果您的规格说明:是的,精确到一分钱,那么您应该努力转换为 DECIMAL。

我曾在一家银行工作过,其中一分钱的错误就被报告为软件缺陷。我尝试(徒劳地)引用软件规范,但该应用程序不需要这种程度的精确度。 (它正在执行许多链式乘法。)我还指出了用户验收测试。 (该软件已被验证并接受。)

唉,有时您只需要进行转换即可。但我鼓励您 A) 确保这对某人很重要,然后 B) 编写测试以表明您的报告准确到指定的程度。

From your comments,

the tax amount rounded to the 4th decimal and the total price rounded
to the 2nd decimal.

Using the example in the comments, I might foresee a case where you have 400 sales of $1.47. Sales-before-tax would be $588.00, and sales-after-tax would sum to $636.51 (accounting for $48.51 in taxes). However, the sales tax of $0.121275 * 400 would be $48.52.

This was one way, albeit contrived, to force a penny's difference.

I would note that there are payroll tax forms from the IRS where they do not care if an error is below a certain amount (if memory serves, $0.50).

Your big question is: does anybody care if certain reports are off by a penny? If the your specs say: yes, be accurate to the penny, then you should go through the effort to convert to DECIMAL.

I have worked at a bank where a one-penny error was reported as a software defect. I tried (in vain) to cite the software specifications, which did not require this degree of precision for this application. (It was performing many chained multiplications.) I also pointed to the user acceptance test. (The software was verified and accepted.)

Alas, sometimes you just have to make the conversion. But I would encourage you to A) make sure that it's important to someone and then B) write tests to show that your reports are accurate to the degree specified.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文