Ulf Wendel

MaxDB Datentypen auf MySQL mappen: Zeichenketten, BLOB/LONG und numerische Datentypen

Seit einigen Wochen arbeite ich mit geringer Priorität an einem MaxDB-Plugin für das MySQL MigrationToolkit. Heute habe ich mir Gedanken über das Mapping von Datentypen gemacht und habe die bisherigen Entscheidungen hinterfragt, die ich einst getroffen hatte, um schnell etwas zu erstellen. Dabei kamen ein paar Details zu Tage, die ich noch nicht kannte.

Inhalt

Stand der Informationen und Warnung

Alle hier angeführten Mappings entsprechen dem was ich heute (27.10.2005) implementiert habe. Diese Mapping ist in keinem Release des MigrationToolkits enthalten und wird es vielleicht auch nie sein. Spätere Versionen des MigrationToolkits können ganz andere Mappings verwenden! Deshalb sollte dieser Blogeintrag nicht als Dokumentation verstanden werden. Es ist mehr ein Blick auf den aktuellen Entwicklungsstand.

Mapping von numerischen Datentypen

Mit MySQL 5.0.3 gab es einige Neuerungen, die das Mapping von Strings beeinflussen. Deshalb ist vor einer Migration zu prüfen, welche MySQL-Version das Zielsystem bildet. Seit MySQL 5.0.3 können VARCHAR Spalten bis zu 65.535 Zeichen aufnehmen anstatt nur 255 Zeichen.

Weiterhin wurden zwei neue Datentypen VARBINARY und BINARY eingeführt, die [VAR]CHAR Spalten ähneln jedoch Binärdaten aufnehmen können. [VAR]BINARY speichert im Gegensatz zu [VAR]CHAR “binary byte strings”. [VAR]CHAR kann zwar mit der Option[BYTE|BINARY] definitert werden, also [VAR]CHAR(n) BINARY, aber das führt nur zu einer binary Collation, etwa CHARACTER SET latin1 COLLATE latin1_bin. Die Collation ändert nichts daran, daß [VAR]CHAR(n) BINARY nur “non-binary character strings” aufnehmen kann.

Letztlich hat sich auch noch das Handling von Leerzeichen am Ende eines [VAR]CHAR Feldes in 5.0.3 verändert. Vor der Version 5.0.3 wurden Leerzeichen abgeschnitten. Das widerspricht dem SQL-Standard. Leider verletzt auch MaxDB 7.6.00 an dieser Stelle den Standard und schneidet ab.

Eine Randbemerkung zu einem kleinen Stolperstein für mich. CHAR ist unter MySQL ein Synonym für CHAR(1). BINARY ein Synonym für BINARY(1). Diese Synonyme finden jedoch keine Entsprechung in VARCHAR und VARCHAR(1) bzw. VARBINARY und VARBINARY(1). VARCHAR und VARBINARY benötigten beide zwingend eine Längenangabe.

Mapping von CHAR

Mapping ab MySQL 5.0.3.

MaxDB MySQL
Typ Länge Typ
CHAR(n) n < = 255 Zeichen CHAR(n)
CHAR(n) 255 < n in Zeichen <= 8000 (Unicode: 4000) TEXT
CHAR(n) ASCII siehe CHAR(n)
CHAR(n) BYTE falls < = 255 Zeichen BINARY(n)
CHAR(n) BYTE 255 < n in Zeichen <= 8000 (Unicode: 4000) BLOB
CHAR(n) UNICODE n < = 255 Zeichen CHAR(n) CHARACTER SET (ucs2)
CHAR(n) UNICODE 255 < n in Zeichen <= 8000 (Unicode: 4000) TEXT CHARACTER SET (ucs2)

Der Datentyp BINARY steht erst ab MySQL 5.0.3 zur Verfügung. Deshalb wird bei allen Versionen vor 5.0.3 der MaxDB Datentyp CHAR(n) BYTE auf TINYBLOB gemappt, sofern die Länge weniger als 256 Zeichen beträgt.

MaxDB MySQL
Typ Länge Typ
CHAR(n) BYTE falls < = 255 Zeichen TINYBLOB

Mapping von VARCHAR

Mapping ab MySQL 5.0.3.

MaxDB MySQL
Typ Länge Typ
VARCHAR(n) 0 <= n in Zeichen <= 8000 (Unicode: 4000) VARCHAR(n)
VARCHAR(n) ASCII siehe VARCHAR(n)
VARCHAR(n) BYTE 0 <= n in Zeichen <= 8000 (Unicode: 4000) VARBINARY(n)
VARCHAR(n) UNICODE 0 <= n in Zeichen <= 8000 (Unicode: 4000) VARCHAR(n) CHARACTER SET (ucs2)

Wird eine MySQL Version kleiner als 5.0.3 verwendet, ergeben sich einige Änderungen, da VARCHAR(n) bis zu dieser Version maximal 255 Zeichen speichern kann. Bei MaxDB Spalten, die mehr als 255 Zeichen enthalten, muß auf die passenden BLOB-Typen ausgewichen werden.
Wie auch oben werden nur die Unterschiede zu 5.0.3+ in der folgenden Tabelle dargestellt.

MaxDB MySQL
Typ Länge Typ
VARCHAR(n) 256 <= n in Zeichen <= 8000 (Unicode: 4000) TEXT
VARCHAR(n) BYTE n in Zeichen <= 255 TINYBLOB
VARCHAR(n) BYTE 256 <= n in Zeichen <= 8000 (Unicode: 4000) BLOB
VARCHAR(n) UNICODE 256 <= n in Zeichen <= 8000 (Unicode: 4000) TEXT CHARACTER SET (ucs2)

Mapping von LONG

LONG Spalten von MaxDB können maximal 4G Bytes aufnehmen. Das macht für ASCII/BYTE 4G Zeichen, bei UNICODE können noch 2G Zeichen gespeichert (2 Byte pro Zeichen). Die MySQL Datentypen LONGTEXT und LONGTYPE können die benötigten 4G Byte Daten aufnehmen.

MaxDB MySQL
Typ Länge Typ
LONG [ASCII] 4G Bytes (4G Zeichen für ASCII/BYTE, 2G Zeichen für Unicode) LONGTEXT
LONG BYTE 4G Bytes LONGBLOB
LONG UNICODE 4G Bytes / 2G Zeichen LONGTEXT CHARACTER SET (ucs2)

Mapping von BOOLEAN

MySQL hat kein Gegenstück zum MaxDB Datentyp BOOLEAN. BOOL und BOOLEAN sind unter MySQL Synonyme für TINYINT(1). In der MT Version von heute ist kein Mapping implementiert.

Mapping von numerischen Datentypen

Das Mapping von numerischen Datentypen ist mit Detailproblemen versehen. FIXED/DECIMAL hat das Problem, daß MySQL maximal 30 Nachkommastellen verwendet, währen MaxDB mit 37 Nachkommastellen aufwartet. FLOAT hat unter MaxDB eine frei wählbare Präzision von bis zu 38 Stellen. Eine derartige Präzision bietet MySQL erst seit der Version 5.0.3 unter dem Schlagwort “Precision Math”. “Precision Math” jedoch ersteckt sich nur auf Fixkommazahlen (DECIMAL, Ganzzahlen – INTEGER, …) nicht jedoch auf Fließkommazahlen.

Mapping von FIXED

FIXED ist unter MySQL ein Synonym für DECIMAL. Der Datentyp FIXED ist ein Fixed Point Datentyp. Es gibt also eine festgelegte Anzahl von Nachkommastellen. Unter MaxDB können maximal 38 signifikante Stellen verwendet werden. Von den 38 Stellen dürfen maximal 37 Nachkommastellen sein. Ab MySQL 5.0.3 kann DECIMAL 65 (zweitweilig 64) Stellen speichern von denen maximal 30 Nachkommastellen sein dürfen. Das bedeutet, daß ein MaxDB FIXED(38, 37) nur unzureichend in MySQL DECIMAL(38,30) gemappt werden kann.

MaxDB MySQL
Typ Länge davon Nachkommastellen Typ
FIXED(p, s) 0 < = p <= 38 s < = p und s <= 30 DECIMAL(p, s)
FIXED(p, s) 0 < = p <= 38 s < = p und s > 30 DECIMAL(p, 30)

Mapping von FLOAT

Wer das unten angegebene SQL im SQL Studio ausführt wird sehen, daß 0.33 und 0.99 ausgegeben werden. Verwendet man unter MySQL den Datentyp FLOAT(2), dann erhält man etwas ähnliches wie 0.333333 und 1.0000000298023.

CREATE TABLE t1 (col_float FLOAT(2))
//
INSERT INTO t1 (col_float) VALUES ( 1 /3 )
//
SELECT col_float, col_float * 3 FROM t1
  

MySQL garantiert nicht, daß bei Verwendung von FLOAT oder DOUBLE präzise Rechnungen durchgeführt werden. Die Berechnungen können sogar von Rechner zu Rechner und Betriebssystem zu Betriebssystem unterschiedlich ausfallen. Der Grund ist, daß MySQL sich auf die Routinen verläßt, die das Betriebssystem bereitstellt. Beim Datentyp FLOAT spricht MySQL deshalb nur von einer Genauigkeit von etwa 7 Stellen und für DOUBLE von etwa 15 Stellen. Erst ab 5.0.3 wird eine eigene Bibliothek für DECIMAL verwendet, was die Genauigkeit auf mindestens 64 Stellen anhebt.

Das MigrationToolkit wertet die Rechengenauigkei höher als die Anzahl der verfügbaren (Nachkomma-)Stellen und mappt deshalb FLOAT auf DECIMAL. Wie bei FIXED tritt jedoch das Problem auf, daß DECIMAL maximal 30 Nachkommastellen speichern kann. Wenn man DECIMAL jedoch 30 Nachkommastellen spendiert, dann bleiben nur noch 65 – 30 = 35 Vorkommastellen übrig. Das reicht bei weitem nicht, um den größten FLOAT-Wert von MaxDB zu speichern. 9. 9999999999999999999999999999999999999E+62 passt einfach nicht in ein DECIMAL(65, 30).

mysql> create table t1(col_decimal decimal(65,30), col_double double);
Query OK, 0 rows affected (0.02 sec)

mysql> insert into t1(col_decimal, col_double) values (9.9E+62, 9.9E+62);
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> show warnings;
+---------+------+---------------------------------------------------------------+
| Level   | Code | Message                                                       |
+---------+------+---------------------------------------------------------------+
| Warning | 1264 | Out of range value adjusted for column 'col_decimal' at row 1 |
+---------+------+---------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select col_decimal, col_double, col_double - col_decimal from t1;
+--------------------------------------------------------------------+------------+--------------------------+
| col_decimal                                                        | col_double | col_double - col_decimal |
+--------------------------------------------------------------------+------------+--------------------------+
| 99999999999999999999999999999999999.999999999999999999999999999999 |    9.9e+62 |                  9.9e+62 |
+--------------------------------------------------------------------+------------+--------------------------+
1 row in set (0.00 sec)

Das MigrationToolkit versucht derzeit gar nicht erst mit Automatismen und Wertebereichsuntersuchungen herauszufinden, welcher Wertebereich und welche Präzision benötigt wird. Hier sollte der Anwender unbedingt manuell prüfen, ob MaxDB in einer FLOAT Spalte Werte enthält die nicht in einem MySQL DECIMAL abgebildet werden können. Ich nehme auch keine Fallunterscheidung vor welche MySQL Version vorliegt. Entweder man hat kleine FLOAT-Werte unter MaxDB und es passt oder man muß die Automatik überstimmen.

Die Entscheidung für DECIMAL(65, p) ist eine der Enscheidungen, die noch zu überprüfen ist.

MaxDB MySQL
Typ Länge Typ
FLOAT(p) 0 < = p <= 38 DECIMAL(65, p) / DECIMAL(64, p)

Mapping von INTEGER und SMALLINT

Das Mapping von Ganzzahlen ist denkbar einfach. Alles bleibt so wie es ist.

MaxDB MySQL
Typ Länge Typ
SMALLINT -32768 – 32767 SMALLINT
INT[EGER] -2147483648 – -2147483647 INTEGER

Fazit – Aufgepasst!

Es existieren einige Tücken im Mapping von numerischen Werten. Direkte Entsprechungen gibt es nicht immer. Man muß genau auf die Wertebereiche und die gewünschte Rechengenauigkeit schauen, um einen passenden Datentyp auszuwählen. Also Achtung beim Versuch MaxDB-Tabellen allzu direkt auf MySQL-Tabellen zu mappen. Morgen geht’s weiter mit den verbleibenden Datentypen: DATE, TIME und TIMESTAMP.