MySQL

Erfahrungen mit dem Zugriff von Delphi auf MySQL

Letztes Update 06.08.2003

Da im Internet die Informationen für den Zugriff auf die MySQL Datenbank mit den DBExpress  Komponenten sehr verteilt sind, kommt hier mal eine kurze Zusammenfassung.


1. Installation von MySQL

Fast ohne Probleme. Die Installation klappt auf Anhieb und mit MySQL-Front kann man auch gleich ein wenig herumprobieren. Das schöne an MySQL-Front ist, das bei allen Aktionen die zugehörigen SQL Befehle protokolliert werden. Da kann man immer schön für das eigene Programm abgucken. Ein Problem scheint es jedoch mit dem Administrator von MySQL zu geben. Immer wenn er gelaufen ist, und ich fahre den Rechner runter bekomme ich einen Blue-Screen.

2. Zugriff über DBExpress

2.1 Umgebungsbedingungen

Delphi

MySQL

2.2 Beispiel

Zusammanhänge der einzelnen Komponenten:

Folgende Funktionen sind in dem Beispiel enthalten:

  1. Öffnen der Datenbank
  2. Schliessen der Datenbank
  3. Wandern durch die Tabelle mit DBNavigator
  4. Erzeugen einer Tabelle mit Primär-Index
  5. Löschen einer Tabelle
  6. Füllen einer Tabelle
  7. Löschen, Ändern, Einfügen einzelner Datensätze
  8. Einfügen und Löschen von Grafiken
  9. Einfügen und Löschen von Memos

Das Testprogramm benötigt zwei Voraussetzungen

1. mylibsql.dll muß in der System32 von Windows liegen. Wenn nicht, dann muß im Programm in sc1.VendorLib der komplette Path angegeben werden.

2. Eine Datenbank mit dem Namen testbase muß vorhanden sein. Die kann z.B. mit MySQL-Front angelegt werden.

2.3 Weitergabe von Programmen

Werden Anwendungen weitergegeben, dann sind folgende drei Bibliotheken mit zu liefern:

DbExpMySql.dll
LibMySql.dll
Midas.dll

2.4 Komponenten zur Laufzeit erzeugen

In Uses-Teil der Interface Sektion müssen folgende Komponenten eingefügt werden:

DB, SqlExpr, FMTBcd, Provider, DBClient, DBLocal, DBLocalS, DBGrids, Grids

Function TForm1.GetEntry:String;
Var sq:String; i:Integer; s1,s2:String;
    scs0:TSQLConnection;sds0:TSQLDataSet;dsp0:TDataSetProvider;
    cds0:TClientDataSet;tds0:TDataSource;grd0:TDBGrid;
Begin
  Result := 'alles klar';
  scs0 := TSQLConnection.Create(Owner);
  sds0 := TSQLDataSet.Create(Owner);
  dsp0 := TDataSetProvider.Create(Owner);
  cds0 := TClientDataSet.Create(Owner);
  tds0 := TDataSource.Create(Owner);
  grd0 := TDBGrid.Create(Owner);
  Try
    Try
      scs0.Name := 'scs0';
      scs0.ConnectionName := 'MSConnection';
      scs0.DriverName := 'MYSQL';
      scs0.GetDriverFunc := 'getSQLDriverMYSQL';
      scs0.LibraryName := 'dbexpmys.dll';
      scs0.LoginPrompt := false;
      scs0.VendorLib := 'LibMySQL.dll';
      sds0.Name := 'sds0';
      sds0.SQLConnection := scs0;
      dsp0.Name := 'dsp0';
      dsp0.DataSet := sds0;
      cds0.Name := 'cds0';
      cds0.ProviderName := 'dsp0';
      tds0.Name := 'tds0';
      tds0.DataSet := cds0;
      grd0.Name := 'grd0';
      grd0.DataSource := tds0;
      scs0.Params.Values['Database'] := 'kunterbunt';
      scs0.Params.Values['Hostname'] := 'localhost';
      scs0.Params.Values['User_Name'] := 'root';
      scs0.Params.Values['Password'] := '';
      sq := 'SELECT * FROM `einfarbig`';
      sds0.CommandText := sq;
      cds0.Active := true;
      If cds0.RecordCount > 0 Then Begin
        For i := 1 to cds0.RecordCount do Begin
          s1 := grd0.Fields[0].AsString;
          s2 := grd0.Fields[1].AsString;
          cds0.Next;
        End;
      End;
    Except
      On E:Exception do Begin
        Result := 'Das war wohl nix';
      End;
    End;
    If cds0.Active Then cds0.Active := false;
  Finally
    FreeAndNil(grd0);
    FreeAndNil(tds0);
    FreeAndNil(cds0);
    FreeAndNil(dsp0);
    FreeAndNil(sds0);
    FreeAndNil(scs0);
  End;
End;

Download

3. Sonstiges

Es ist darauf zu achten, das der Tabellenname nicht in ' ($27) steht, sondern in ` ($60). Durch dieses Zeichen wird ein Name als Tabellename identifiziert, während ein Name mit ' als String interpretiert wird.

Es ist darauf zu achten, das keine Feldnamen verwendet werden, die mit SQL-Befehlen identisch sind (also kein Feldname der Select heisst). Sollte sich das trotzdem nicht vermeiden lassen, ist der Feldname in `Select` zu setzen.

3.1 Datenformat

Die Datenbank arbeitet mit dem Internationalen Datumsformat, d.h. beim Speichern eines Datums muß das Format umgeschaltet werden:

Var sd:String; sp:Char;
Begin
  sd := ShortDateFormat;
  sp := DateSeparator;
  ShortDateFormat := 'YYYY-MM-DD';
  DateSeparator := '-';
  Try
    ...
  Finally
    ShortDateFormat := sd;
    DateSeparator := sp;
  End;
End;

Wenn jedoch ein Datum in einem Grid angezeigt wird, erscheint es im Rechnerformat (DD.MM.YYYY) und kann auch so eingegeben werden. Ursache unbekannt.

3.2 Float Format

Aufpassen muß man beim Speichern von Float-Zahlen. Im Deutschen ist der Delimeter ein Komma. Ein Komma ist aber auch das Trennungszeichen im INSERT Befehl:

INSERT INTO testtable (Integer1, Real1) VALUES (10, 1,34)

So etwas führt zu der berechtigten Fehlermeldung, das die Anzahl der Argumente nicht korrekt ist.

Wenn eine Float-Zahl in einen String umgewandelt wird, muß als Delimeter ein Punkt eingestellt werden.

Var sd:Char;
Begin
  sd := DecimalSeparator;
  DecimalSeparator := '.';
  Try
    ...
  Finally
    DecimalSeparator := sd;
  End;
End;

Dann sieht das ganze so aus:

INSERT INTO testtable (Integer1, Real1) VALUES (10, 1.34)

3.3 Speichern und Lesen von binären Strings

Das Speichern eines binären String funkioniert nicht bei folgenden Typen:

CHAR (x)
CHAR (x) BINARY
VARCHAR (x)
VARCHAR (x) BINARY
TEXT
TINYTEXT
MEDIUMTEXT
LONGTEXT

Unter Delphi kann in einem String jedes Zeichen zwischen $00 und $FF stehen. Zwischen dem INSERT Befehl und dem Speichern in der Datenbanktabelle werden die Zeichen:

$00 = Null, $27 = Esc und $5C = \

einfach geschluckt. Das Backslash kann man noch speichern wenn man es doppelt angibt, bei \\ wird nur ein \ gespeichert. (Überprüft mit einem HEX-Editor).

Wenn man also einen binären String speichern möchte, geht das nur als Stream.

Procedure StrToStream(s1:String;st:TMemoryStream);
begin
  st.Write(s1[1],Length(s1));
End;

Function StreamToStr(st:TStream):String;
begin
  SetLength(Result,st.Size);
  st.Position := 0;
  st.Read(Result[1],st.Size);
End;

procedure TMermaid.StringWriteButtonClick(Sender: TObject);
Var s1,s2:String; x1:TMemoryStream; i:Integer; bf:TBlobField;
Var ts:TStrings;
begin
  x1 := TMemoryStream.Create;
  s2 := '<';
  For i := 0 to 255 do s2 := s2 + Chr(i);
  s2 := s2 + '>';
  Try
    s1 := 'SELECT * FROM `test1`';
    SQLDataSet1.CommandText := s1;
    Try
      ClientDataSet1.Active := true;
      bf := TBlobField(ClientDataSet1.FieldByName('Str1'));
      ClientDataSet1.Edit;
      StrToStream(s2,x1);
      bf.LoadFromStream(x1);

      ClientDataSet1.Post;
      ClientDataSet1.ApplyUpdates(0);
      ClientDataSet1.Active := false;
    Except
      On E:Exception do Begin
        Logout(E.Message);
      End;
    End;
  Finally
    FreeAndNil(x1);
  End;
end;

procedure TMermaid.StringReadButtonClick(Sender: TObject);
Var s1,s2:String; x1:TStream; i:Integer; BlobField:TField;
begin
  s1 := 'SELECT * FROM `test1`';
  SQLDataSet1.CommandText := s1;
  Try
    ClientDataSet1.Active := true;
    Blobfield := ClientDataSet1.FieldByName('Str1');
    x1 := ClientDataSet1.CreateBlobStream(BlobField,bmRead);
    Try
      If ClientDataSet1.FieldByName('Str1').IsNull Then Begin
        Logout('Stream ist leer');
      End Else Begin
        s2 := StreamToStr(x1);
        For i := 1 to Length(s2) do Begin    
          Logout(IntToHex(Ord(s2[i]),2));
        End;
      End;
    Finally
      FreeAndNil(x1);
    End;
    ClientDataSet1.Active := false;
  Except
    On E:Exception do Begin
      Logout(E.Message);
    End;
  End;
end;

3.4 Geschwindigkeit

Die Ursache für den Umstieg auf MySQL liegt in der Abkündigung der BDE. Da die BDE für die Verwaltung von Messwerten benutzt wird, ist eine optimale Lesegeschwindigkeit sehr wichtig. Um optimale Zugriffszeiten auf die Datenbank zu bekommen, wurden diese für einige Komponenten ermittelt:

Gelesen wurden jeweils 10000 Datensätze aus 10 verschiedenen Tabellen. Die folgenden Zeiten ist der Mittelwert aus 100000 Datensätzen.

Paradox Table-Zugriff
Write: 0,25 ms
Save: 3,68 ms
Read: 0,26 ms

Paradox SQL-Zugriff
Write: 4,47 ms
Save: 5,65 ms
Read: 0,07 ms

MySQL DBExpress-Componente, SQL-Zugriff
Write: 0,09 ms
Save: 2,32 ms
Read: 0,09 ms

MySQL ZeosLib Componente, SQL-Zugriff
Write: 14,5 ms
Save: 16,0 ms
Read: 0,49 ms

MySQL DAC Componente, SQL-Zugriff
Write: 2,12 ms
Save: 3,71 ms
Read: 0,38 ms

MySQL CorelLab Components, SQL-Zugriff
Write: 13,7 ms
Save: 24,4 ms
Read: 1,08 ms

Es konnten keine Zeitunterschiede festgestellt werden, zwischen dem Lesen/Schreiben der ersten 10000 Datensätzen und den letzten 10000 Datensätzen.