Discussion:
Datenbank kopieren
(zu alt für eine Antwort)
Jakob Achterndiek
2015-04-04 08:27:20 UTC
Permalink
Hallo,

in einem (mit Delphi 5 geschriebenen) Programm verwalte ich eine
umfangreiche MS-Access-Datenbank mit über 2000 Sätzen zu je 25
Feldern Personendaten und etwa 500 Sätzen zu je 20 Feldern
Adressdaten. Diese Datenbank kopiere ich (mit demselben Programm)
in eine mySQL-Dstenbank. Die Übertragung dauert etwa 5 Minuten.
Das erscheint mir zu langsam.

Die entscheidenden Zeilen sind (vereinfacht) ganz konventionell:
Quelle.Table.Open;
Ziel.Table.Open;
Quelle.Table.First;
repeat
Ziel.Table.Append;
for i := 0 to Quelle.Table.FieldCount - 1 do
Ziel.Table.Fields[i].Value := Quelle.Table.Fields[i].Value;
Quelle.Table.Next;
until Quelle.Table.eof;
Ziel.Table.Post;
Ziel.Table.Close;
Quelle.Table.CLose;

Meine Frage:
Kann man da dieses schluckzessive Stapeln etwas beschleunigen?
Vielleicht dadurch, daß man vorher schon mal den benötigten Platz
reservieren läßt?

Für hilfreiche Vorschläge wäre ich dankbar.

Gruß
j/\a
--
Peter Below (TeamB)
2015-04-04 09:10:33 UTC
Permalink
Post by Jakob Achterndiek
Hallo,
in einem (mit Delphi 5 geschriebenen) Programm verwalte ich eine
umfangreiche MS-Access-Datenbank mit über 2000 Sätzen zu je 25
Feldern Personendaten und etwa 500 Sätzen zu je 20 Feldern
Adressdaten. Diese Datenbank kopiere ich (mit demselben Programm)
in eine mySQL-Dstenbank. Die Übertragung dauert etwa 5 Minuten.
Das erscheint mir zu langsam.
Quelle.Table.Open;
Ziel.Table.Open;
Quelle.Table.First;
repeat
Ziel.Table.Append;
for i := 0 to Quelle.Table.FieldCount - 1 do
Ziel.Table.Fields[i].Value := Quelle.Table.Fields[i].Value;
Quelle.Table.Next;
until Quelle.Table.eof;
Ziel.Table.Post;
Ziel.Table.Close;
Quelle.Table.CLose;
Kann man da dieses schluckzessive Stapeln etwas beschleunigen?
Vielleicht dadurch, daß man vorher schon mal den benötigten Platz
reservieren läßt?
Für eine Server-Datenbank sollte man niemals TTable und Abkömmlinge
verwenden, sondern eine SQL query mit einem entsprechenden INSERT
Statement zum Anlegen neuer Datensätze. Allerdings ist Delphi 5
dermaßen hoffnungslos veraltet, dass Du sicher keine auch nur halbwegs
aktuellen Treiber für die Verbindung mit einer aktuellen mySQL-Version
finden wirst.
--
Peter Below (TeamB)
Jakob Achterndiek
2015-04-04 10:23:21 UTC
Permalink
Am 04.04.2015, 11:10 Uhr, schrieb Peter Below (TeamB)
Post by Peter Below (TeamB)
Post by Jakob Achterndiek
in einem (mit Delphi 5 geschriebenen) Programm verwalte ich eine
umfangreiche MS-Access-Datenbank mit über 2000 Sätzen zu je 25
Feldern Personendaten und etwa 500 Sätzen zu je 20 Feldern
Adressdaten. Diese Datenbank kopiere ich (mit demselben Programm)
in eine mySQL-Dstenbank. Die Übertragung dauert etwa 5 Minuten.
Das erscheint mir zu langsam.
[..]
Kann man da dieses schluckzessive Stapeln etwas beschleunigen?
Vielleicht dadurch, daß man vorher schon mal den benötigten Platz
reservieren läßt?
Für eine Server-Datenbank sollte man niemals TTable und Abkömmlinge
verwenden, sondern eine SQL query mit einem entsprechenden INSERT
Statement zum Anlegen neuer Datensätze. Allerdings ist Delphi 5
dermaßen hoffnungslos veraltet, dass Du sicher keine auch nur halbwegs
aktuellen Treiber für die Verbindung mit einer aktuellen mySQL-Version
finden wirst.
Die Datenbanken liegen auf meinem Rechner.
Als Treiber verwende ich die Teile aus "MicroOLAP DAC for MySQL"
und für MS-Access die aus DiamondAccess. Als uralter Pascalist habe
ich mich schon vor Jahrzehnten mit Delphi angefreundet.

Deinen Vorschlag, Table-Funktionen durch Query-Funktionen zu ersetzen,
werde ich gern ausprobieren. Danke einstweilen.

j/\a
--
Alfred Gemsa
2015-04-05 06:03:39 UTC
Permalink
Post by Peter Below (TeamB)
Post by Jakob Achterndiek
Quelle.Table.Open;
Ziel.Table.Open;
Quelle.Table.First;
repeat
Ziel.Table.Append;
for i := 0 to Quelle.Table.FieldCount - 1 do
Ziel.Table.Fields[i].Value := Quelle.Table.Fields[i].Value;
Quelle.Table.Next;
until Quelle.Table.eof;
Ziel.Table.Post;
Ziel.Table.Close;
Quelle.Table.CLose;
Für eine Server-Datenbank sollte man niemals TTable und Abkömmlinge
verwenden, sondern eine SQL query mit einem entsprechenden INSERT
Statement zum Anlegen neuer Datensätze....
Erklärst du mir warum?

Alfred.
Peter Below (TeamB)
2015-04-05 08:04:22 UTC
Permalink
Post by Alfred Gemsa
Post by Peter Below (TeamB)
Post by Jakob Achterndiek
Quelle.Table.Open;
Ziel.Table.Open;
Quelle.Table.First;
repeat
Ziel.Table.Append;
for i := 0 to Quelle.Table.FieldCount - 1 do
Ziel.Table.Fields[i].Value := Quelle.Table.Fields[i].Value;
Quelle.Table.Next;
until Quelle.Table.eof;
Ziel.Table.Post;
Ziel.Table.Close;
Quelle.Table.CLose;
Für eine Server-Datenbank sollte man niemals TTable und Abkömmlinge
verwenden, sondern eine SQL query mit einem entsprechenden INSERT
Statement zum Anlegen neuer Datensätze....
Erklärst du mir warum?
Ein TTable lädt praktisch die gesamte Tabelle in den Arbeitsspeicher.
Das ist OK für Datenbanken, bei denen die Datenbankengine eh in das
eigenen Programm gelinkt wird, das Programm also die vollständige
Arbeit mit den Daten selbst machen muß (auch wenn ein Teil in externen
DLLs steckt).

Eine Serverdatenbank arbeitet ganz anders. Die wird von einem separaten
Programm verwaltet, auch bei einer lokalen Installation (auf windows
ist das meistens ein Service). Das Anwenderprogramm schickt Anfragen
(Queries) und Anweisungen (zum Anlegen, Ändern, Löschen von
Datensätzen) an den Server über das Netzwerk und bekommte Ergebnisse
zurück. Der Server ist von vornherein darauf angelegt, viele Nutzer
parallel zu bedienen. Damit das effizient funktioniert sollte jedes
Anwenderprogramm nur die Daten anfordern, die der Benutzer auch
wirklich zum arbeiten braucht. Einfach den Inhalt einer ganzen Tabelle
anzufordern und dann den Inhalt auf der Clientseite zu filtern ist da
ein echter Kunstfehler. Er belastet nicht nur das Netzwerk unnötig, in
einer echten Multiuser-Umgebung kann ja auch jederzeit ein Datensatz
auf dem Server von einem anderen Benutzer geändert werden. Je weniger
Datensätze der Client gerade geladen hat desdo kleiner ist die Chance,
das einer davon von einer solchen Änderung getroffen wird und der
Client dann nicht mehr den aktuellen Zustand des Datensatzes anzeigt.
--
Peter Below (TeamB)
Joachim Duerr
2015-04-05 11:57:50 UTC
Permalink
Post by Peter Below (TeamB)
Ein TTable lädt praktisch die gesamte Tabelle in den Arbeitsspeicher.
das muss nicht sein, ist aber bei den meisten leider so implementiert.
Aber eine Query mit SELECT * könnte u.U. noch schädlicher sein, weil
eine Table-Komponente sequentiell liest, eine Query oft alles
überträgt. Bei Abfragen mit Einschränkung (select * from table where
id=10) gilt das natürlich nicht.
Wenn aber der OP die komplette Tabelle kopieren möchte, so wäre hier
zum Lesen eine Table wesentlich angebrachter - sofern er sie geöffnet
lässt.
--
Joachim Duerr
Alfred Gemsa
2015-04-06 11:11:05 UTC
Permalink
Post by Peter Below (TeamB)
Post by Alfred Gemsa
Post by Peter Below (TeamB)
Post by Jakob Achterndiek
Quelle.Table.Open;
Ziel.Table.Open;
Quelle.Table.First;
repeat
Ziel.Table.Append;
for i := 0 to Quelle.Table.FieldCount - 1 do
Ziel.Table.Fields[i].Value := Quelle.Table.Fields[i].Value;
Quelle.Table.Next;
until Quelle.Table.eof;
Ziel.Table.Post;
Ziel.Table.Close;
Quelle.Table.CLose;
Für eine Server-Datenbank sollte man niemals TTable und Abkömmlinge
verwenden, sondern eine SQL query mit einem entsprechenden INSERT
Statement zum Anlegen neuer Datensätze....
Erklärst du mir warum?
Ein TTable lädt praktisch die gesamte Tabelle in den Arbeitsspeicher.
Das ist OK für Datenbanken, bei denen die Datenbankengine eh in das
eigenen Programm gelinkt wird, das Programm also die vollständige
Arbeit mit den Daten selbst machen muß (auch wenn ein Teil in externen
DLLs steckt).
Eine Serverdatenbank arbeitet ganz anders....
Hm,

wir benutzen Firebird als Server und die Interbase-Komponenten
(IBDatabase, IBTransaction und zum Datenzugriff IBDataset).

Die Tabelle in der Datenbank hat so 300.000 Datensätze, und beim
"Append" eines Records, Besetzen der Felder und Post gibt's öfters einen
"Out of Memory-Error", nicht immer, kaum reproduzierbar.

Kann es sein, dass auch hier SQL-Query die angesagte Methode ist?

BTW, auch IBExpert stürzt ab, wenn man versucht, die Tabellendaten bis
zum EOF auf den Schirm zu bringen, wobei ich eine ältere Version
IBExpert habe (2005), bei der das nie auftritt.

Ich weiß nicht, wo man da ansetzen soll, Firebird (Version 2.0 oder 2.5)
sollte ja wohl in der Lage sein, einige 100.000 Datensätze locker zu
verarbeiten.

Alfred.
Matthias Hanft
2015-04-06 13:52:56 UTC
Permalink
Post by Alfred Gemsa
wir benutzen Firebird als Server und die Interbase-Komponenten
(IBDatabase, IBTransaction und zum Datenzugriff IBDataset).
Statt IBDataset benutze ich hier seit 20 Jahren nur noch IBSQL.
Da schreibt man die Abfrage (ggf. mit Parametern) im Objekt-
inspektor rein (z.B. 'SELECT NAME FROM KUNDEN WHERE ID=:ID;'),
und im Code steht dann etwa

IBTransaction.StartTransaction;
with IBSQL do begin
// ggf. Prepare, falls man das öfters mit unterschiedlichen Parametern aufruft
ParamByName('ID').AsInteger:=myKundennummer; // falls es Parameter gibt
ExecQuery;
while not Eof do begin
myKundenname:=FieldByName('NAME').AsString; // oder was halt abgefragt wurde
Next
end;
Close;
// ggf. UnPrepare, falls man Prepare gemacht hat
end;
IBTransaction.Commit;

Das ist schnell und schlank und überträgt nur die angeforderten
Daten und macht ganz bestimmt niemals Speicherprobleme :-)

Zum Einfügen analog dazu (z.B. 'INSERT INTO KUNDEN (ID) VALUES (:ID);')

IBTransaction.StartTransaction;
with IBSQL do begin
Prepare;
for I:=1 to 10000 do begin
ParamByName('ID').AsInteger:=I;
ExecQuery
end;
UnPrepare
end;
IBTransaction.Commit;

Kürzer, schneller und noch weniger speicherhungrig gehts vermutlich nicht...

Gruß Matthias.
Peter Below (TeamB)
2015-04-07 16:24:58 UTC
Permalink
Post by Alfred Gemsa
wir benutzen Firebird als Server und die Interbase-Komponenten
(IBDatabase, IBTransaction und zum Datenzugriff IBDataset).
Meinst Du IBTable? IBDataset gibt es nicht, jedenfalls nicht in neueren
Versionen von IBX.
Post by Alfred Gemsa
Die Tabelle in der Datenbank hat so 300.000 Datensätze, und beim
"Append" eines Records, Besetzen der Felder und Post gibt's öfters
einen "Out of Memory-Error", nicht immer, kaum reproduzierbar.
Kann es sein, dass auch hier SQL-Query die angesagte Methode ist?
Definitiv. IBQuery statt IBTable.

Außerdem wird Firebird nicht offiziell von den IBX-Komponenten
unterstützt. Interbase und Firebird driften halt mit jeder neuen
Version weiter auseinander, und das betrifft auch das API, auf dem IBX
aufsetzt.
--
Peter Below (TeamB)
Matthias Hanft
2015-04-07 16:56:34 UTC
Permalink
Post by Peter Below (TeamB)
Außerdem wird Firebird nicht offiziell von den IBX-Komponenten
unterstützt. Interbase und Firebird driften halt mit jeder neuen
Version weiter auseinander, und das betrifft auch das API, auf dem IBX
aufsetzt.
Das liest man zwar immer wieder, aber in der Praxis funktionieren TIB{SQL|
Database|Transaction} von Delphi 7 aber auch noch prima mit Firebird 2.5.
BTDT (zumindest mit "Standard-SQL"; irgendwelche möglicherweise versions-
abhängigen internen Firebird-Tricks verwende ich nicht).

Gruß Matthias.
Matthias Hanft
2015-04-07 17:06:28 UTC
Permalink
Post by Matthias Hanft
Das liest man zwar immer wieder, aber in der Praxis funktionieren TIB{SQL|
Database|Transaction} von Delphi 7 aber auch noch prima mit Firebird 2.5.
...wobei ich allerdings die ursprünglich bei Delphi mitgelieferte "gds32.dll"
stets durch den "fbclient.dll" der jeweils verwendeten Serverversion ersetze
(inkl. Rename in "gds32.dll", damit Delphi sie findet).

Gruß Matthias.

Peter J. Holzer
2015-04-04 09:15:06 UTC
Permalink
Post by Jakob Achterndiek
in einem (mit Delphi 5 geschriebenen) Programm verwalte ich eine
umfangreiche MS-Access-Datenbank mit über 2000 Sätzen zu je 25
Feldern Personendaten und etwa 500 Sätzen zu je 20 Feldern
Adressdaten. Diese Datenbank kopiere ich (mit demselben Programm)
in eine mySQL-Dstenbank. Die Übertragung dauert etwa 5 Minuten.
Das erscheint mir zu langsam.
Also etwa 8 Records pro Sekunde. Ja, das ist ziemlich langsam.
Post by Jakob Achterndiek
Quelle.Table.Open;
Ziel.Table.Open;
Quelle.Table.First;
repeat
Ziel.Table.Append;
for i := 0 to Quelle.Table.FieldCount - 1 do
Ziel.Table.Fields[i].Value := Quelle.Table.Fields[i].Value;
Quelle.Table.Next;
until Quelle.Table.eof;
Ziel.Table.Post;
Ziel.Table.Close;
Quelle.Table.CLose;
Das ist jetzt wahrscheinlich mehr eine Delphi-Frage als eine
MySQL-Frage, da in dem Code nichts MySQL-spezifisches vorkommt. Die
Datenbankzugriffe erledigt Delphi offenbar hinter den Kulissen, und es
ist nicht erkennbar, was es macht, und warum das langsam ist.

Prinzipiell gilt allerdings bei Datenbank-Performance-Problemen die
Faustregel, dass es hilfreich ist, die Zahl der Querys zu verringern:
Jede Query muss zum Server geschickt, dort verarbeitet und das Ergebnis
wieder zurückgeschickt werden. Wenn die Query nur sehr wenig macht (z.B.
eine Zeile in eine Datenbank einfügt), ist das ein nicht-vernachlässig-
barer Overhead.

Und da sind ORM-Systeme, die die eigentlichen Datenbankzugriffe
verstecken, ziemlich gefährlich. Was macht z.B. die Zeile
Post by Jakob Achterndiek
Ziel.Table.Fields[i].Value := Quelle.Table.Fields[i].Value;
in Deinem Programm? Schreibt die den Wert gleich in die Datenbank oder
nur in einen Puffer, der irgendwann später geleert wird?

Wenn ersteres, haben wir das Problem wahrscheinlich identifiziert: Denn
die Zeile wird 2000 * 25 + 500 * 20 = 60000 mal aufgerufen. Wenn das
jedesmal einen Datenbankzugriff bedeutet, dann sind das 200 Zugriffe pro
Sekunde. Das klingt schon recht plausibel.

Wenn letzteres: Wann wird dieser Puffer geleert? Hast Du darauf
irgendeinen Einfluss?

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Jakob Achterndiek
2015-04-04 10:23:06 UTC
Permalink
Post by Peter J. Holzer
Post by Jakob Achterndiek
Ziel.Table.Fields[i].Value := Quelle.Table.Fields[i].Value;
in Deinem Programm? Schreibt die den Wert gleich in die Datenbank
oder nur in einen Puffer, der irgendwann später geleert wird?
Denn die Zeile wird 2000 * 25 + 500 * 20 = 60000 mal aufgerufen.
Wenn das jedesmal einen Datenbankzugriff bedeutet, dann sind das
200 Zugriffe pro Sekunde. Das klingt schon recht plausibel.
Wenn letzteres: Wann wird dieser Puffer geleert? Hast Du darauf
irgendeinen Einfluss?
Danke für den Hinweis. Dem werde ich auch mal nachgehen.

j/\a
--
Matthias Hanft
2015-04-04 12:56:19 UTC
Permalink
Post by Peter J. Holzer
Post by Jakob Achterndiek
Ziel.Table.Fields[i].Value := Quelle.Table.Fields[i].Value;
in Deinem Programm? Schreibt die den Wert gleich in die Datenbank oder
nur in einen Puffer, der irgendwann später geleert wird?
Ich glaub', "Table"-Komponenten sind für sowas ziemlich giftig, weil die
unter Umständen ganze Tabellen lesen und/oder schreiben, und das am Ende
noch bei jedem einzelnen Zugriff. Ich hab' die eigentlich nie verwendet,
sondern (für Firebird) immer nur TIBSQL, wo man selber die Kontrolle drüber
hat, was gelesen und/oder geschrieben wird. Diese "nackten" SQL-Queries
gibts sicher für alle Arten von Datenbanken...

Und: Ich kenn' mich jetzt mit Access und MySQL nicht sooo gut aus, aber bei
Firebird hilft es, vor umfangreichen INSERTs explizit eine Transaktion zu
öffnen und danach wieder zu schließen - denn AutoCommits nach jedem einzelnen
INSERT bei vielen Records ist ebenfalls ein heftiger Geschwindigkeitskiller...

Gruß Matthias.
Jakob Achterndiek
2015-04-04 21:44:56 UTC
Permalink
Post by Peter J. Holzer
Post by Jakob Achterndiek
Ziel.Table.Fields[i].Value := Quelle.Table.Fields[i].Value;
in Deinem Programm? Schreibt die den Wert gleich in die Datenbank
oder nur in einen Puffer, der irgendwann später geleert wird?
Ich glaub', "Table"-Komponenten sind für sowas ziemlich giftig, [..]
Ich habe auf der Ziel-Seite tTable durch tQuery ersetzt. Das
erforderte etwas mehr Bastelei im Delphi-Programm, hat aber
die Zeit fürs Kopieren von 04:23:504 auf 03:08:076 (mm:ss:zzz)
schon mal deutlich verkürzt.

Einstweilen besten Dank für alle Hinweise. Ich werde noch ein
bißchen weiter damit spielen.

Gruß
j/\a
--
Lesen Sie weiter auf narkive:
Loading...