Подтвердить что ты не робот

DOUBLE vs DECIMAL в MySQL

ОК, поэтому я знаю, что есть много статей, в которых говорится, что я не должен использовать DOUBLE для хранения денег в базе данных MySQL, иначе у меня появятся сложные ошибки. Дело в том, что я не разрабатываю новую базу данных, я прошу найти способ оптимизации существующей системы. Более новая версия содержит 783 DOUBLE типизированных столбцов, большинство из которых используется для хранения денег или формулы для расчета суммы денег.

Итак, мое первое мнение по этому вопросу было я настоятельно рекомендую преобразовать из DOUBLE в DECIMAL в следующей версии, потому что doc MySQL и все так говорят. Но тогда я не мог найти подходящего аргумента, чтобы оправдать эту рекомендацию по трем причинам:

  • Мы не выполняем вычисления в базе данных. Все операции выполняются на Java с использованием BigDecimal, а MySQL используется просто для хранения результатов.
  • Точность в 15 цифр, которую предлагает DOUBLE, достаточно много, так как мы храним в основном суммы с 2 десятичными цифрами и иногда небольшие числа с 8 десятичными знаками для аргументов формулы.
  • У нас есть 6-летний рекорд в производстве без известной проблемы с ошибкой из-за потери точности на стороне MySQL.

Даже выполняя операции над таблицей строк в 18 миллинов, например SUM и сложными умножениями, я не мог выполнить ошибку отсутствия точности. И мы фактически не делаем такого рода вещи в производстве. Я могу показать потерю точности, выполняя что-то вроде

SELECT columnName * 1.000000000000000 FROM tableName;

Но я не могу понять, как превратить его в ошибку во второй десятичной цифре. Большинство реальных проблем, которые я нашел в Интернете, - это записи 2005 года и старше в форуме, и я не смог воспроизвести их на сервере MySQL 5.0.51.

Итак, до тех пор, пока мы не выполним никаких арифметических операций SQL, которые мы не планируем делать, есть ли какая-то проблема, которую мы должны ожидать от хранения и возврата суммы денег в столбце DOUBLE?

4b9b3361

Ответ 1

На самом деле это совсем другое. DOUBLE вызывает проблемы округления. И если вы делаете что-то вроде 0.1 + 0.2, оно дает вам что-то вроде 0.30000000000000004. Я лично не буду доверять финансовым данным, использующим математику с плавающей запятой. Воздействие может быть небольшим, но кто знает. Я бы предпочел, чтобы то, что я знаю, было достоверным, чем данные, которые были аппроксимированы, особенно когда вы имеете дело с денежными значениями.

Ответ 2

Пример из документации 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)

В принципе, если вы суммируете a, вы получаете 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)

Ответ 3

Мы только что прошли эту же проблему, но наоборот. То есть мы храним суммы в долларах как DECIMAL, но теперь мы находим, что, например, MySQL вычислял значение 4,389999999993, но при хранении этого в поле DECIMAL он сохранял его как 4.38 вместо 4.39, как мы хотели это к. Таким образом, хотя DOUBLE может вызвать проблемы округления, кажется, что DECIMAL может также вызвать некоторые проблемы усечения.

Ответ 4

"есть ли какой-либо вопрос, который мы должны ожидать от хранения и возврата суммы денег в колонке DOUBLE?"

Похоже, что в вашем сценарии не может быть произведено ошибок округления, и если бы они были, они были бы усечены преобразованием в BigDecimal.

Так что я бы сказал нет.

Однако нет никакой гарантии, что некоторые изменения в будущем не приведут к проблеме.

Ответ 5

Из ваших комментариев,

сумма налога округляется до 4-го десятичного знака, а общая цена округляется до второго десятичного знака.

Используя пример в комментариях, я могу предвидеть случай, когда у вас 400 продаж в размере 1,47 доллара. Продажи перед уплатой налогов составили бы 588 долларов США, а суммарный налог будет составлять 636,51 доллара США (с учетом налогов в размере 48,51 доллара США). Однако налог с продаж в размере $0,121275 * 400 составит $48,52.

Это был один из способов, хотя и надуманный, чтобы заставить разницу в пенни.

Я бы отметил, что в IRS есть налоговые формы для расчета заработной платы, где им все равно, если ошибка ниже определенной суммы (если память обслуживается, 0,50 доллара США).

Ваш большой вопрос: кто-нибудь заботится, если некоторые отчеты отключены копейкой? Если ваши спецификации говорят: да, быть точным с копейкой, тогда вы должны пройти через попытку конвертировать в DECIMAL.

Я работал в банке, где была зарегистрирована ошибка с одним пенни как дефект программного обеспечения. Я попытался (зря) указать спецификации программного обеспечения, которые не требовали такой степени точности для этого приложения. (Он выполнял множественные умножения.) Я также указал на тест приёма пользователей. (Программное обеспечение было проверено и принято.)

Увы, иногда вам просто нужно сделать преобразование. Но я бы посоветовал вам: A) убедиться, что это важно для кого-то, а затем B) написать тесты, чтобы показать, что ваши отчеты точны до указанной степени.