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 Christian 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
Nersgatt hat folgendes geschrieben : |
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
Lemmy hat folgendes geschrieben : |
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:20
Nersgatt hat folgendes geschrieben : |
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
NOS hat folgendes geschrieben : |
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
NOS hat folgendes geschrieben : |
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
NOS hat folgendes geschrieben : |
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:
| 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; |
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:
| con.LoginPrompt := false; con.ConnectionDefName := ConDefName; con.TxOptions.ReadOnly := false; con.TxOptions.AutoCommit := true; con.TxOptions.AutoStart := true; con.TxOptions.AutoStop := true; 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.LockWait := true; con.ResourceOptions.CmdExecMode := amBlocking; con.ResourceOptions.MacroExpand := false; con.ResourceOptions.CmdExecTimeout := 60000; con.ResourceOptions.DirectExecute := true; con.OnError := FBConOnErrorHandler; con.connected := true; {$IFDEF CONNECTIONINTERFACEMONITORING} con.ConnectionIntf.Tracing := True; {$ENDIF} conquery.Connection := con; conquery.CachedUpdates := false; conquery.FetchOptions.AutoClose := false; 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.LockWait := true; conquery.ResourceOptions.CmdExecMode := amBlocking; conquery.ResourceOptions.MacroExpand := false; conquery.ResourceOptions.CmdExecTimeout := 60000; conquery.ResourceOptions.DirectExecute := true; 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
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2024 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!