Entwickler-Ecke

Alle Sprachen - Alle Plattformen - Mehr als einen Datensatz mit einem Rutsch in DB schreiben


NOS - Mi 13.07.16 22:14
Titel: Mehr als einen Datensatz mit einem Rutsch in DB schreiben
Hallo zusammen,

ich schreibe datensätze in einer schleife in eine firebird embedded db ... das ganze multithreaded ... ich frage mich ob es geschwindigkeit bringt und ob es überhaupt die möglichkeit gibt das hinzufügen der Datensätze oder besser gesagt mehrerer datensätze am stück zu erledigen ... ich nhutze firebird embedded in der letzten 2.5.x release mit firedac und delphi berlin

Ich würde mich freuen wenn ihr mir helfen könntet.

LG,

Andreas


Moderiert von user profile iconChristian S.: Topic aus Datenbanken (inkl. ADO.NET) verschoben am Mi 13.07.2016 um 22:37


Nersgatt - Do 14.07.16 06:48

Klar, kann man mehr als einen Datensatz auf einmal einfügen:


SQL-Anweisung
1:
2:
3:
4:
5:
INSERT INTO tabelle
(ID, FELD)
VALUES
(1'BLA'),
(2'BLUBB');


Aber pass auf, die maximale Größe der Statements ist beschränkt.

Wenn Du viele Datensätze einfügst, dann kann das durchaus Geschwindigkeitsvorteile bringen.


Lemmy - Do 14.07.16 07:06

user profile iconNersgatt hat folgendes geschrieben Zum zitierten Posting springen:
Klar, kann man mehr als einen Datensatz auf einmal einfügen:


das mag vielleicht für MySQL und co gelten, aber in Firebird wäre mir das neu....


Nersgatt - Do 14.07.16 07:10

Ich war mir sehr sicher, dass Firebird das auch kann. Ist schon wieder 2 1/2 Jahre her, dass ich nicht mehr mit Firebird arbeite... :?
Aber auch für Firebird gibt es eine Lösung: http://www.firebirdfaq.org/faq336/
Ich würde dabei die Execute Block-Lösung bevorzugen.


erfahrener Neuling - Do 14.07.16 08:08

Moin
user profile iconLemmy hat folgendes geschrieben Zum zitierten Posting springen:


das mag vielleicht für MySQL und co gelten, aber in Firebird wäre mir das neu....

Wieso sollte es denn nicht gehen? die Syntax von Nersgatt war doch richtig!?

Gruß
Julian


Lemmy - Do 14.07.16 09:18

user profile iconerfahrener Neuling hat folgendes geschrieben Zum zitierten Posting springen:
Moin
user profile iconLemmy hat folgendes geschrieben Zum zitierten Posting springen:


das mag vielleicht für MySQL und co gelten, aber in Firebird wäre mir das neu....

Wieso sollte es denn nicht gehen? die Syntax von Nersgatt war doch richtig!?


dann zeigs mir - ich lerne gerne dazu..... Aber bitte mit Firebird, nicht mit MySQL ;-)


Lemmy - Do 14.07.16 09:20

user profile iconNersgatt hat folgendes geschrieben Zum zitierten Posting springen:

Ich würde dabei die Execute Block-Lösung bevorzugen.


stimmt das wäre in der Tat eine Variante. wäre interessant ob diese wirklich schneller ist als über einen Insert mit manueller Tranaktionssteuerung....


erfahrener Neuling - Do 14.07.16 09:25

Naja wie schon gesagt: Die von Nersgatt gepostete Syntax ist richtig (also insert into Tabelle (var1, var2, ...) values (value1, value2, ...) und wird auch öfters von mir verwendet. Allgemein hab ich bis jetzt noch gar keine so großen Unterschiede zu SQL entdecken können. (arbeite jetzt vllt seit 2 Monaten damit)

Langsam kriege ich aber das Gefühl, dich und den Sachverhalt missverstanden zu haben :gruebel: :les:
Und mein Gefühl hat sich bestätigt...


Ralf Jansen - Do 14.07.16 09:38

Ich bezweifle das bei Firebird Embedded es irgendeinen größeren Unterschied gibt ob ich die Datensätze einzeln oder en bloc zur Engine schiebe. Wir bewegen uns schließlich im gleichen Prozessraum. Und die Engine wird letztlich beim einfügen mit den Daten das gleiche je Datensatz tun egal ob ich ein Single Datensatz Statement dahingeschickt habe oder ein Multi Datensatz Statement.


Nersgatt - Do 14.07.16 09:44

Einen Versuch ist es immerhin wert, ob es Performancevorteile bringt.

Wenn man einen Datensatz mit einem Statement einfügt, sollte man aber auf jeden Fall ein Prepared-Statement nutzen. Sonst muss die Datenbank ja jedes Statement erneut parsen, optimieren, usw., was definitiv spürbar Performance kostet.


NOS - Do 14.07.16 10:01

Zunächst einmal vielen Dank euch allen ... wenn ich es recht verstehe ist diese Variante also sehr langsam zum adden in die db ????


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
      try
       conquery.Params.Clear;
       conquery.Sql.Text := 'INSERT INTO URLSourceTable(PARENTID, URL, URLSOURCE) VALUES (:PARENTID, :URL, :URLSOURCE)';
       conquery.ParamByName('PARENTID').AsInteger := FCurrentID;
       conquery.ParamByName('URLSOURCE').AsString := FURLSource.DataString;
       conquery.ParamByName('URL').AsString := FCurrentURL;
       conquery.ExecSql;
       conquery.Close;
      except
       on E : Exception do
        if DomainCrawler.LOGMode = lmFile then
         DomainCrawler.WriteToLOGFile('CoreEngine Error 100080 (UrlworkerExecute - Update URLSourceTable) - [' + IntToStr(ThreadID) + '] - ' + E.Message);
      end;


Nersgatt - Do 14.07.16 10:06

user profile iconNOS hat folgendes geschrieben Zum zitierten Posting springen:
Zunächst einmal vielen Dank euch allen ... wenn ich es recht verstehe ist diese Variante also sehr langsam zum adden in die db ????

Nein, nicht unbedingt.
Nur solltest Du drauf achten, dass Du Zeile 2 und 3 nur einmal ausführst. Zeile 4 -7 packst Du in Deine Schleife (befüllst die Parameter der Query also immer mit neuen Werten und fügst dann ein). Zeile 8 kannst Du Dir schenken.


Lemmy - Do 14.07.16 10:06

user profile iconNOS hat folgendes geschrieben Zum zitierten Posting springen:
Zunächst einmal vielen Dank euch allen ... wenn ich es recht verstehe ist diese Variante also sehr langsam zum adden in die db ????



naja, du schreibst da halt einen Datensatz in die DB. Weder das Umfeld noch der Thread ist hier erkennbar. Darüber hinaus kann meiner Meinung nach multithreaded Inserts in FIrebird embedded nicht funktionieren, weil auf die Embedded nur eine Connection erlaubt ist (zumindest in FB 2.5) - d.h. da wird dann die Connection irgend wann zum Flaschenhals.

Und wenn Du den gezeigten Code mehrfach (in einer Schleife) ausführst, dann solltest Du die SQL-Zuweisung außerhalb der Schleife machen, sonst verlierst Du den Vorteil der Parameter und die Query muss den Insert bei jeder Ausführung erneut parsen.


NOS - Do 14.07.16 10:19

Du meinst also ich soll den Teil hier aus der Schleife lassen


Delphi-Quelltext
1:
conquery.Sql.Text := 'INSERT INTO URLSourceTable(PARENTID, URL, URLSOURCE) VALUES (:PARENTID, :URL, :URLSOURCE)';                    


Das multithreaded adden funktioniert über connection pooling


Martok - Do 14.07.16 10:44

Wie handhabt FBE Transaktionen? Wenn du parallel arbeitest muss da ja auch irgendwo eine Transaktion drumrum sein... falls da noch irgendwo ein Autocommit ist dürfte das das langsamste sein.


Lemmy - Do 14.07.16 10:49

user profile iconNOS hat folgendes geschrieben Zum zitierten Posting springen:
Du meinst also ich soll den Teil hier aus der Schleife lassen


Jens hat es doch deutlich geschrieben:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
  //außerhalb der schleife
  conquery.Sql.Text := 'INSERT INTO URLSourceTable(PARENTID, URL, URLSOURCE) VALUES (:PARENTID, :URL, :URLSOURCE)';
....

//innerhalb der schleife

       conquery.ParamByName('PARENTID').AsInteger := FCurrentID;
       conquery.ParamByName('URLSOURCE').AsString := FURLSource.DataString;
       conquery.ParamByName('URL').AsString := FCurrentURL;
       conquery.ExecSql;


Ob ein ClearParams notwendig ist bitte bei den Komponenten nachschlagen, habe ich noch nie gebraucht.

Und dann bitte auch den Einwurf von matrok beachten - bei so was ist manuelle Transactionsteuerung Pflicht und dürfte (wenn man nach jedem Insert ein Commit absetzt - bewusst oder unbewusst) die größte Bremse sein


NOS - Do 14.07.16 10:51

Die Connection hat in den Settings ein AutoCommit = true .... nun bin ich nicht der DB spezialist ... was bedeutet eine transaction drumrum ?

hier mal der Codeblock zum einrichten von conection und query


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
 // general options
  con.LoginPrompt := false;
  con.ConnectionDefName := ConDefName;
  // Tx options
  //con.TxOptions.Isolation := xiSnapshot;
  con.TxOptions.ReadOnly := false;
  con.TxOptions.AutoCommit := true;
  con.TxOptions.AutoStart := true;
  con.TxOptions.AutoStop := true;
  //con.TxOptions.DisconnectAction := xdCommit;
  // update options
  con.UpdateOptions.ReadOnly := false;
  con.UpdateOptions.EnableInsert := true;
  con.UpdateOptions.EnableUpdate := true;
  con.UpdateOptions.EnableDelete := false;
  con.UpdateOptions.RequestLive := true;
  con.UpdateOptions.UpdateMode := upWhereKeyOnly;
  con.UpdateOptions.UpdateChangedFields := true;
  con.UpdateOptions.CheckRequired := false;
  con.UpdateOptions.CheckReadOnly := false;
  con.UpdateOptions.CheckUpdatable := true;
  //con.UpdateOptions.RefreshMode := rmOnDemand;
  //con.UpdateOptions.LockMode := lmPessimistic;
  con.UpdateOptions.LockWait := true;
  //con.UpdateOptions.LockPoint := lpImmediate;
  // ressource options
  con.ResourceOptions.CmdExecMode := amBlocking;
  con.ResourceOptions.MacroExpand := false;
  con.ResourceOptions.CmdExecTimeout := 60000;
  con.ResourceOptions.DirectExecute := true;
  // error handler
  con.OnError := FBConOnErrorHandler;
  con.connected := true;
  {$IFDEF CONNECTIONINTERFACEMONITORING}
   con.ConnectionIntf.Tracing := True;
  {$ENDIF}
  // set query options and add connection
  conquery.Connection := con;
  // general options
  conquery.CachedUpdates := false;
  // fetch options
  //conquery.FetchOptions.Mode := fmOnDemand;
  conquery.FetchOptions.AutoClose := false;
  // update options
  conquery.UpdateOptions.ReadOnly := false;
  conquery.UpdateOptions.EnableInsert := true;
  conquery.UpdateOptions.EnableUpdate := true;
  conquery.UpdateOptions.EnableDelete := false;
  conquery.UpdateOptions.RequestLive := true;
  conquery.UpdateOptions.UpdateMode := upWhereKeyOnly;
  conquery.UpdateOptions.UpdateChangedFields := true;
  conquery.UpdateOptions.CheckRequired := false;
  conquery.UpdateOptions.CheckReadOnly := false;
  conquery.UpdateOptions.CheckUpdatable := true;
  //conquery.UpdateOptions.RefreshMode := rmOnDemand;
  //conquery.UpdateOptions.LockMode := lmPessimistic;
  conquery.UpdateOptions.LockWait := true;
  //conquery.UpdateOptions.LockPoint := lpImmediate;
  // ressource options
  conquery.ResourceOptions.CmdExecMode := amBlocking;
  conquery.ResourceOptions.MacroExpand := false;
  conquery.ResourceOptions.CmdExecTimeout := 60000;
  conquery.ResourceOptions.DirectExecute := true;
  // error handler
  conquery.OnPostError := FBOnPostErrorHandler;
  conquery.OnEditError := FBOnEditErrorHandler;
  conquery.OnError := FBOnErrorHandler;


NOS - Mi 27.07.16 20:46

Hallo zusammen,

also ich habe es nun soweit umgebaut dass das AutoCommit raus ist ... mal zum Verständnis und zum Speed

wenn ich also in jedem Thread, in dem ich ja in mehrere Tables schreibe, für jede Table eine Query erzeuge müsste ich doch, da die Query das SQL Statement nicht mehr parsen, muss auch massiv zeit sparen ... oder ?


Nersgatt - Mo 01.08.16 15:44

Als Grundregel kann man sagen, dass es immer sinnvoll ist, wiederkehrende Sql-Statements nur einmal vorzubereiten und dann über Parameter immer neu zu befüllen.
Wie viel Performance man damit rausholt hängt immer vom Anwendungszweck ab.

Ich hab mal bei einem Arbeitgeber Importschnittstellen angepasst, wo gerne mal 1000 Datensätze importiert wurden.
Vorher war es so - wie man es oft bei Anfängern sieht - dass für jeden Datensatz ein Sql-String zusammen gebastelt wurde und an die Datenbank geschickt. Umgebaut zu einer parametrisierten Abfrage, einmal vorbereiten, 1000 mal befüllen -> schwupps ist die Importschnittstelle um den Faktor 10 schneller (gefühlt). Chef hat sich gefreut.
Bei solchen Konstellationen kann man also enorm einsparen.


NOS - Mo 01.08.16 15:49

Hallo Nersgatt,

danke für die Info ... ich habe es in den letzten Tagen so umgebaut dass es nun für jede Table in die ich während der Analyse schreibe auch eine separate TFDQuery gibt und ich das SQL Statement nur einmal zu Anfang vorbereite bzw. übergebe und das wars.

Zitat:
einmal vorbereiten, 1000 mal befüllen
bedeutet aber trotz allem in diesem fall 1000 zugriffe auf die db oder ging das in einem rutsch ?


Nersgatt - Mo 01.08.16 15:51

Das sind auch 1000 Zugriffe auf die Datenbank. Wobei aber jedes Mal nur die Werte für die Parameter übertragen werden. Nicht mehr das ganze Statement.


NOS - Mo 01.08.16 15:57

Ok ... also so wie ich es jetzt auch habe ...

einmal die vorbereitung


Delphi-Quelltext
1:
  conAddParentURLQuery.Sql.Text := 'INSERT INTO ParentURLTable(PID, URL) VALUES (:PID, :URL)';                    


und dann das adden in einer schleife


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
   try
    conAddParentURLQuery.ParamByName('PID').AsInteger := FCurrentID;
    conAddParentURLQuery.ParamByName('URL').AsString := AddURL;
    conAddParentURLQuery.ExecSql;
   except
    on E: Exception do
     if DomainCrawler.LOGMode = lmFile then
      DomainCrawler.WriteToLOGFile('CoreEngine Error 100020 (AddItemToDataBase - Insert ParentURLTable) - [' + IntToStr(ThreadID) + '] - ' + E.Message + ' | ' + AddURL + ' | ' + FCurrentURL);
   end;


wie kann man solch zugriffe noch optimieren von der geschwindigkeit her ? gibt es da noch möglichkeiten ?


Nersgatt - Mo 01.08.16 16:23

Du kannst außerdem noch vor der Schleife eine Transaktion starten, und sie nach der Schleife committen. Das bringt noch ein bisschen.

Wobei man da abwägen muss, ob das sinnvoll ist. Wenn ein Datensatz nicht angenommen wird (warum auch immer), werden in dem Fall natürlich alle Datensätze nicht angenommen.
Und ich würde keine langlaufenden Transaktionen haben wollen, wenn es nicht für die Datenintegrität absolut notwendig ist. Langlaufende Transaktionen können auch wieder Nachteile haben. Du musst abwägen, wie lange die Schleife läuft und ob es Dir der Geschwindigkeitsvorteil wert ist, eventuelle Nachteile in Kauf zu nehmen.


NOS - Mo 01.08.16 16:30

Es ist so dass ich mehrere Threads laufen habe die mit jeweils einer Connection aus einem Pool eine URL in dem Thread downloaden, analysieren und ich dann im anschluss an die analyse die listen mit den Links, HTags etc. in die DB schreiben will ... ich glaube es macht in dem Falle so keinen Sinn aber ich werde es trotzdem mal austesten.


Ralf Jansen - Mo 01.08.16 17:00

Zitat:
analysieren und ich dann im anschluss an die analyse die listen mit den Links, HTags etc. in die DB schreiben will ... ich glaube es macht in dem Falle so keinen Sinn aber ich werde es trotzdem mal austesten.


Das Ergebnis einer solchen Analyse klingt doch schon nach genau einer Transaktion. Wer will schon ein Teilergebnis in der Datenbank oder mit anderen Analysen (beim anlegen) verknüpft haben?
Primär sollte das Ding ja auch erstmal das Richtige tun. Das es das auch schnell macht kommt später. Dabei kann man sich dann im Zweifel überlegen ob man Korrektkeit/Vollständigkeit für Performance aufgeben muß.


NOS - Mo 01.08.16 17:17

Hi Ralf,

das richtige tut es schon :) Funktioniert einwandfrei, sammelt korrekt alle Daten und speichert auch alles entsprechend ab.

Den WebsiteSpider gibt es hier kostenlos -> https://seoboxx.com/de/produkte/websitespider

Ich baue die Engine grad um und habe einen neuen Parser geschrieben und die DB umgebaut und wollte danach ein wenig "Performance" und "Speed" in die Analyse bringen sofern möglich.

Nächster Schritt ist der Umbau auf FB 3.0 embedded