MySQL中的DOUBLE和DECIMAL

好的,所以我知道有很多文章指出我不应该使用DOUBLE在MySQL数据库中存储资金,否则我会以棘手的精确错误结束。 重点是我没有devise一个新的数据库,我要求find方法来优化现有的系统。 新版本包含783个DOUBLEtypes的列,其中大部分用于存储货币或公式来计算金额。

所以我对这个主题的第一个看法是我应该强烈推荐在下一个版本中将DOUBLE转换为DECIMAL,因为MySQL文档和大家都这么说。 但是,我找不到任何合理的论据来certificate这个build议,原因有三:

  • 我们不对数据库执行任何计算。 所有的操作都是使用BigDecimal在Java中完成的,MySQL只是作为结果的普通存储。
  • DOUBLE提供的15位精度是足够的,因为我们主要存储2位十进制数字,偶尔小数字8位十进制数字作为公式参数。
  • 我们有6年的生产logging,因为MySQL方面的精度损失而没有已知的bug。

即使通过在一个18毫米行表上执行操作(如SUM和复数乘法),我也无法执行缺less精度的错误。 而我们实际上并没有在生产中做这种事情。 我可以通过做类似的事情来显示丢失的精确度

SELECT columnName * 1.000000000000000 FROM tableName;

但我无法find一种方法将其转换成小数点后第二位的错误。 我在互联网上发现的大多数真正的问题是2005年和较旧的论坛条目,并且我无法在5.0.51 MySQL服务器上重现它们中的任何一个。

所以只要我们不执行任何我们不打算做的SQL算术运算,我们应该期待什么问题,只需要在DOUBLE列中存储和读取金额?

其实这是完全不同的。 DOUBLE导致四舍五入问题。 如果你做了0.1 + 0.2这样的事情,就会给你0.30000000000000004这样的0.30000000000000004 。 我个人不会相信使用浮点math的财务数据。 影响可能很小,但谁知道。 我宁愿拥有我所知道的可靠数据,而不愿意接近数据,特别是在处理货币价值的时候。

从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 arithemetic操作,你可以使用DOUBLE,但我仍然更喜欢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) 

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

“有没有什么问题我们只需要在DOUBLE栏中储存和提取金额?”

这听起来似乎没有舍入错误可以在您的scheme中产生,如果有的话,他们将被截断转换为BigDecimal。

所以我会说不。

但是,不能保证将来有些变化不会造成问题。

从你的意见,

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

通过评论中的例子,我可以预见一个你有400个销售额为1.47美元的案例。 税前销售额为588.00美元,税后销售额为636.51美元(税收为48.51美元 )。 但是,$ 121275 * 400的销售税将是$ 48.52。

这是一个方法,尽pipe人为设法强迫一分钱的差别。

我会注意到,有IRS的工资税表,他们不关心如果错误低于一定的数额(如果内存服务,$ 0.50)。

你最大的问题是: 有人关心某些报告是否被一分钱? 如果你的规格说:是的,准确的一分钱,那么你应该通过努力转换为十进制。

我曾经在一家银行工作,那里有一分钱的错误报告为软件缺陷。 我试过(徒劳无功)引用软件规范,对于这个应用程序不需要这个精度。 (正在执行很多链式乘法)。我还指出了用户验收testing。 (该软件已被validation并被接受。)

唉,有时你只需要进行转换。 但是,我鼓励你:A)确保对某人很重要,然后B)写testing来certificate你的报告是准确的。