web-dev-qa-db-de.com

DOUBLE vs DECIMAL in MySQL

OK, ich weiß, dass es Unmengen von Artikeln gibt, die besagen, dass ich DOUBLE nicht verwenden sollte, um Geld in einer MySQL-Datenbank zu speichern, oder ich werde mit heiklen Präzisionsfehlern enden. Der Punkt ist, dass ich keine neue Datenbank entwerfe, sondern einen Weg finde, um ein bestehendes System zu optimieren. Die neuere Version enthält 783 DOUBLE-typisierte Spalten, von denen die meisten zum Speichern von Geld oder Formeln zur Berechnung des Geldbetrags verwendet werden.

Meine erste Meinung zu diesem Thema war also, dass ich eine Konvertierung von DOUBLE nach DECIMAL in der nächsten Version sehr empfehlen sollte, weil das MySQL-Dokument und alle das so sagen. Aber dann konnte ich aus drei Gründen kein gutes Argument finden, um diese Empfehlung zu rechtfertigen:

  • Wir führen keine Berechnung für die Datenbank durch. Alle Operationen werden in Java mit BigDecimal ausgeführt, und MySQL wird nur als einfacher Speicher für Ergebnisse verwendet.
  • Die Genauigkeit von 15 Stellen, die ein DOUBLE bietet, ist ausreichend, da wir hauptsächlich Beträge mit 2 Dezimalstellen und gelegentlich kleine Zahlen mit 8 Dezimalstellen für Formelargumente speichern.
  • Wir haben einen 6-Jahres-Rekord in der Produktion, bei dem aufgrund eines Genauigkeitsverlustes auf der MySQL-Seite kein Fehler aufgetreten ist.

Selbst wenn ich Operationen an einer 18-Millons-Zeilentabelle wie SUMME und komplexe Multiplikationen durchführe, kann ich keinen Fehler wegen mangelnder Genauigkeit machen. Und solche Dinge machen wir in der Produktion eigentlich nicht. Ich kann die verlorene Präzision zeigen, wenn ich so etwas mache

SELECT columnName * 1.000000000000000 FROM tableName;

Aber ich kann nicht herausfinden, wie ich es bei der 2. Dezimalstelle in einen Fehler verwandeln kann. Die meisten echten Probleme, die ich im Internet gefunden habe, sind Foreneinträge von 2005 und älter, und ich konnte keines davon auf einem 5.0.51 MySQL-Server reproduzieren.

So lange wir keine SQL-Rechenoperationen ausführen, die wir nicht planen, gibt es ein Problem, das wir erwarten sollten, wenn wir nur einen Geldbetrag in einer DOUBLE-Spalte speichern und zurückerhalten?

63
user327961

Eigentlich ist es ganz anders. DOUBLE führt zu Rundungsproblemen. Und wenn du so etwas machst wie 0.1 + 0.2 es gibt dir so etwas wie 0.30000000000000004. Ich persönlich würde Finanzdaten, die Gleitkomma-Mathematik verwenden, nicht vertrauen. Die Auswirkungen mögen gering sein, aber wer weiß. Ich hätte lieber zuverlässige Daten als Daten, die angenähert wurden, insbesondere wenn Sie mit Geldwerten zu tun haben.

46
bash-

Das Beispiel aus der MySQL-Dokumentation http://dev.mysql.com/doc/refman/5.1/en/problems-with-float.html (Ich verkleinere es, Dokumentation für diesen Abschnitt ist das gleiche für 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)

Wenn Sie a addieren, erhalten Sie 0-13,2 + 59,6 + 30,4 = 76,8. Wenn wir b addieren, erhalten wir 0 + 0 + 46,4 + 30,4 = 76,8. Die Summe von a und b ist die gleiche, aber die MySQL-Dokumentation sagt:

Ein Gleitkommawert, wie er in einer SQL-Anweisung geschrieben ist, stimmt möglicherweise nicht mit dem intern dargestellten Wert überein.

Wenn wir dasselbe mit Dezimal wiederholen:

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)

Das Ergebnis ist wie erwartet leer gesetzt.

Solange Sie keine SQL-Arithmetikoperationen ausführen, können Sie DOUBLE verwenden, ich würde jedoch DECIMAL bevorzugen.

Eine weitere Anmerkung zu DECIMAL ist das Runden, wenn der Bruchteil zu groß ist. Beispiel:

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)
34
broadband

Wir haben gerade dasselbe Thema durchlaufen, aber umgekehrt. Das heißt, wir speichern Dollarbeträge als DEZIMAL, aber jetzt stellen wir fest, dass MySQL beispielsweise einen Wert von 4,389999999993 berechnet hat, aber beim Speichern im Feld DEZIMAL wurde dieser Wert als 4,38 statt wie gewünscht als 4,39 gespeichert es zu. Obwohl DOUBLE Rundungsprobleme verursachen kann, scheint es, dass DECIMAL auch einige Kürzungsprobleme verursachen kann.

16
Mark

Aus Ihren Kommentaren,

der Steuerbetrag wird auf die 4. Dezimalstelle und der Gesamtpreis auf die 2. Dezimalstelle gerundet.

Unter Verwendung des Beispiels in den Kommentaren könnte ich einen Fall vorhersehen, in dem Sie 400 Verkäufe von 1,47 USD erzielen. Der Umsatz vor Steuern würde 588,00 USD betragen, und der Umsatz nach Steuern würde 636,51 USD betragen (dies entspricht 48,51 USD Steuern). Die Umsatzsteuer von 0,121275 USD * 400 USD würde jedoch 48,52 USD betragen

Dies war eine, wenn auch erfundene Möglichkeit, die Differenz eines Pennys zu erzwingen.

Ich würde bemerken, dass es Lohnsteuerformulare von der IRS gibt, bei denen es ihnen egal ist, ob ein Fehler unter einem bestimmten Betrag liegt (wenn Speicher dient, 0,50 $).

Ihre große Frage lautet: Interessiert es jemanden, wenn bestimmte Berichte um einen Cent abweichen? Wenn in Ihren Angaben steht: Ja, auf den Cent genau, dann sollten Sie sich bemühen, auf DECIMAL umzurechnen.

Ich habe bei einer Bank gearbeitet, bei der ein Ein-Cent-Fehler als Softwarefehler gemeldet wurde. Ich habe (vergeblich) versucht, die Software-Spezifikationen zu zitieren, die für diese Anwendung nicht diese Genauigkeit erforderten. (Es wurden viele verkettete Multiplikationen durchgeführt.) Ich verwies auch auf den Benutzerakzeptanztest. (Die Software wurde verifiziert und akzeptiert.)

Leider muss man manchmal nur die Konvertierung vornehmen. Aber ich würde Sie ermutigen, A) sicherzustellen, dass es für jemanden wichtig ist, und dann B) Tests zu schreiben, um zu zeigen, dass Ihre Berichte bis zu dem angegebenen Grad genau sind.

0
rajah9

"Gibt es ein Problem, das wir erwarten sollten, wenn wir nur einen Geldbetrag in einer DOUBLE-Spalte speichern und zurückerhalten?"

Es hört sich so an, als ob in Ihrem Szenario keine Rundungsfehler auftreten könnten, die bei der Konvertierung nach BigDecimal abgeschnitten würden.

Also würde ich nein sagen.

Es gibt jedoch keine Garantie dafür, dass eine Änderung in der Zukunft kein Problem mit sich bringt.

0
Steve Wellens