Dapper Bulk Insert Zurückgegebene Serien-IDs

8

Ich versuche, eine Masseneinfügung durchzuführen, die Dapper über Npgsql verwendet, die die IDs der neu eingefügten Zeilen zurückgibt. Die folgende INSERT-Anweisung wird in meinen beiden Beispielen verwendet:

%Vor%

Zuerst habe ich versucht, ein Array von Objekten mit einer "Value" -Eigenschaft hinzuzufügen:

%Vor%

Dies schlägt jedoch mit der NpgsqlException fehl: "FEHLER: 42703: Spalte" Wert "existiert nicht". Nach dem Lesen dieser Frage I dachte, dass ich vielleicht ein DataTable-Objekt anstelle eines Objektarrays übergeben muss:

%Vor%

Dies schlägt jedoch mit genau der gleichen Ausnahme fehl. Wie kann ich eine Masseneinfügung durchführen und die resultierenden Serien-IDs aus Dapper über Npgsql herausholen?

Ich habe bemerkt, dass die Umrandung der Ausnahme nicht mit dem Spaltennamen übereinstimmt, aber ich bin mir sicher, dass ich um die Tabellen- und Spaltennamen herum Zitate habe, also bin ich mir nicht sicher, warum es "Wert" statt "Wert" sagt "In der Ausnahme. Nur gedacht, ich würde es erwähnen, falls es irgendwie mit dem Fehler zusammenhängt, da es einfach ist, Gehäuse zu übersehen.

- BEARBEITEN -

Um dies zu verdeutlichen, ist dies die SQL zum Erstellen der Tabelle

%Vor%

Und mit den oben definierten Variablen "query" und "values" ist dies der Code, der auf Zeilenbasis arbeitet:

%Vor%

Das Problem ist, dass ich Hunderter (vielleicht Tausende in der nahen Zukunft) von Zeilen pro Sekunde in "MyTable" einfügen muss. Das Warten darauf, dass diese Schleife iterativ jeden Wert an die Datenbank sendet, ist umständlich und (I nehmen an, aber haben noch Benchmark) zeitaufwendig. Außerdem führe ich zusätzliche Berechnungen für die Werte durch, die zu zusätzlichen Einfügungen führen können oder auch nicht, bei denen ich einen Fremdschlüsselverweis auf den Eintrag "MyTable" benötige.

Wegen dieser Probleme suche ich nach einer Alternative, die alle Werte in einer einzigen Anweisung an die Datenbank sendet, um den Netzwerkverkehr und die Verarbeitungslatenz zu reduzieren. Nochmals, ich habe den iterativen Ansatz noch NICHT bewertet. Was ich suche, ist eine Alternative, die einen Bulk-Insert erstellt, sodass ich die beiden Ansätze gegeneinander messen kann.

    
Jeff G 13.04.2015, 21:29
quelle

1 Antwort

10

Letztlich habe ich vier verschiedene Ansätze für dieses Problem entwickelt. Ich habe 500 zufällige Werte generiert, um sie in MyTable einzufügen, und habe jeden der vier Ansätze zeitlich abgestimmt (einschließlich Starten und Zurückrollen der Transaktion, in der sie ausgeführt wurde). In meinem Test befindet sich die Datenbank auf localhost. Die Lösung mit der besten Leistung erfordert jedoch auch nur einen Umlauf zum Datenbankserver. Daher sollte die beste Lösung, die ich gefunden habe, die Alternativen bei der Bereitstellung auf einem anderen Server als der Datenbank übertreffen.

Beachten Sie, dass die Variablen connection und transaction im folgenden Code verwendet werden und als gültige Npgsql-Datenobjekte gelten. Beachten Sie auch, dass die Notation Nx langsamer angibt, dass eine Operation eine Zeit benötigt, die der optimalen Lösung multipliziert mit N entspricht.

Approach # 1 (1.494 ms = 18.7x langsamer): Rollt das Array in einzelne Parameter auf

%Vor%

Ich bin mir wirklich nicht sicher, warum das am langsamsten war, da es nur einen einzigen Umlauf in die Datenbank erfordert, aber es war.

Ansatz Nr. 2 (267 ms = 3,3x langsamer): Standardschleifeniteration

%Vor%

Ich war schockiert, dass dies nur 2x langsamer war als die optimale Lösung, aber ich würde erwarten, dass das in der realen Umgebung deutlich schlechter wird, da diese Lösung 500 Nachrichten seriell an den Server senden muss. Dies ist jedoch auch die einfachste Lösung.

Ansatz # 3 (223 ms = 2,8x langsamer): Asynchrone Schleifeniteration

%Vor%

Dies wird besser, ist aber immer noch weniger als optimal, da wir nur so viele Einfügungen in die Warteschlange stellen können, wie es verfügbare Threads im Thread-Pool gibt. Dies ist jedoch fast so einfach wie der Ansatz ohne Threads. Daher ist es ein guter Kompromiss zwischen Geschwindigkeit und Lesbarkeit.

Ansatz Nr. 4 (134 ms = 1,7x langsamer): Bulk-Inserts

Für diesen Ansatz muss das folgende Postgres-SQL definiert werden, bevor das darunter liegende Codesegment ausgeführt wird:

%Vor%

Und der dazugehörige Code:

%Vor%

Es gibt zwei Probleme, die ich mit diesem Ansatz habe. Der erste ist, dass ich die Reihenfolge der Mitglieder von MyTableType fest codieren muss. Wenn sich diese Reihenfolge ändert, muss ich diesen Code anpassen. Die zweite ist, dass ich alle Eingabewerte in eine Zeichenfolge konvertieren muss, bevor ich sie an Postgres sende (im echten Code habe ich mehr als eine Spalte, so dass ich nicht einfach die Signatur der Datenbankfunktion ändern kann, um ein Double zu erhalten Genauigkeit [], es sei denn, ich übergebe N Arrays, wobei N die Anzahl der Felder auf MyTableType ist).

Trotz dieser Fallstricke nähert sich dies dem Ideal und erfordert nur einen Hin- und Rückweg zur Datenbank.

- BEGIN BEARBEITEN -

Seit dem ursprünglichen Beitrag habe ich vier weitere Ansätze entwickelt, die alle schneller sind als die oben aufgeführten. Ich habe die Nx langsameren Zahlen geändert, um die neue schnellste Methode unten zu reflektieren.

Ansatz # 5 (105 ms = 1,3x langsamer): Wie # 4, ohne dynamische Abfrage

Der einzige Unterschied zwischen diesem Ansatz und Ansatz # 4 ist die folgende Änderung der Funktion "InsertIntoMyTable":

%Vor%

Zusätzlich zu den Problemen mit Ansatz # 4 besteht der Nachteil darin, dass in der Produktionsumgebung "MyTable" partitioniert ist. Unter Verwendung dieses Ansatzes benötige ich eine Methode pro Zielpartition.

Ansatz Nr. 6 (89 ms = 1,1x langsamer): Anweisung mit Array-Argument einfügen

%Vor%

Der einzige Nachteil ist der gleiche wie bei der ersten Ausgabe mit Approach # 4 . Nämlich, dass es die Implementierung an die Reihenfolge von "MyTableType" koppelt. Dennoch war dies mein zweiter bevorzugter Ansatz, da es sehr schnell ist und keine Datenbankfunktionen benötigt, um korrekt zu funktionieren.

Approach # 7 (80ms = SEHR etwas langsamer): Wie # 1, aber ohne Parameter

%Vor%

Das ist meine Lieblingsmethode. Es ist nur marginal langsamer als das schnellste (selbst mit 4000 Datensätzen läuft es immer noch unter 1 Sekunde), benötigt aber keine speziellen Datenbankfunktionen oder -typen. Das einzige, was mir nicht gefällt, ist, dass ich die doppelten Präzisionswerte stringieren muss, um dann von Postgres wieder herausgefiltert zu werden. Es wäre vorzuziehen, die Werte im Binärformat zu senden, so dass sie 8 Bytes anstelle der massiven 20 oder so Bytes, die ich ihnen zugewiesen habe, aufgenommen haben.

Approach # 8 (80ms): Wie # 5, aber in pure sql

Der einzige Unterschied zwischen diesem Ansatz und Ansatz # 5 ist die folgende Änderung der Funktion "InsertIntoMyTable":

%Vor%

Dieser Ansatz benötigt wie # 5 eine Funktion pro "MyTable" Partition. Dies ist am schnellsten, da der Abfrageplan für jede Funktion einmal generiert und dann wiederverwendet werden kann. Bei den anderen Ansätzen muss die Abfrage geparst, dann geplant und dann ausgeführt werden.Obwohl dies der schnellste ist, habe ich es aufgrund der zusätzlichen Anforderungen auf der Datenbankseite über Approach # 7 nicht ausgewählt, mit sehr geringem Geschwindigkeitsvorteil.

    
Jeff G 16.04.2015, 00:14
quelle

Tags und Links