Angesichts des Codes in Ihrer Antwort würden Sie höchstwahrscheinlich die Leistung verbessern, indem Sie die folgenden zwei Änderungen vornehmen:
Starten Sie den Abfragestapel mit BEGIN TRANund beenden Sie den Stapel mit COMMIT TRAN:
Verringern Sie die Anzahl der Aktualisierungen pro Stapel auf weniger als 5000, um eine Eskalation der Sperren zu vermeiden (die im Allgemeinen bei 5000 Sperren auftritt). Versuchen Sie 4500.
Wenn Sie diese beiden Schritte ausführen, sollten Sie die enorme Anzahl von Trans-Log-Schreibvorgängen und Sperr- / Entsperrvorgängen verringern, die Sie derzeit durch Ausführen einzelner DML-Anweisungen generieren.
Beispiel:
conn.Open();
using (SqlCommand cmd = new SqlCommand(
@"BEGIN TRAN;
UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 1;
UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 2;
...
UPDATE [TestTable] SET Column5 = 'some unique value' WHERE ID = 4500;
COMMIT TRAN;
", conn));
AKTUALISIEREN
Die Frage ist etwas spärlich in den Details. Der Beispielcode wird nur in einer Antwort angezeigt .
Ein Bereich der Verwirrung besteht darin, dass in der Beschreibung die Aktualisierung von zwei Spalten erwähnt wird, der Beispielcode jedoch nur eine einzelne Spalte zeigt, die aktualisiert wird. Meine Antwort oben basierte auf dem Code, daher wird nur eine einzige Spalte angezeigt. Wenn wirklich zwei Spalten aktualisiert werden müssen, sollten beide Spalten in derselben UPDATEAnweisung aktualisiert werden :
conn.Open();
using (SqlCommand cmd = new SqlCommand(
@"BEGIN TRAN;
UPDATE [TestTable]
SET Column5 = 'some unique value',
ColumnN = 'some unique value'
WHERE ID = 1;
UPDATE [TestTable]
SET Column5 = 'some unique value',
SET ColumnN = 'some unique value'
WHERE ID = 2;
...
UPDATE [TestTable]
SET Column5 = 'some unique value',
SET ColumnN = 'some unique value'
WHERE ID = 4500;
COMMIT TRAN;
", conn));
Ein weiteres unklares Problem ist, woher die "eindeutigen" Daten stammen. In der Frage wird erwähnt, dass die eindeutigen Werte GUIDs sind. Werden diese in der App-Ebene generiert? Kommen sie aus einer anderen Datenquelle, die die App-Schicht kennt und die Datenbank nicht? Dies ist wichtig, da es abhängig von den Antworten auf diese Fragen sinnvoll sein kann, folgende Fragen zu stellen:
- Können die GUIDs stattdessen in SQL Server generiert werden?
- Wenn ja zu # 1, gibt es dann einen Grund, diesen Code aus App-Code zu generieren, anstatt eine einfache Batch-Schleife in T-SQL durchzuführen?
Wenn "Ja" zu # 1, aber der Code, aus welchem Grund auch immer, in .NET generiert werden muss, können Sie Anweisungen verwenden NEWID()und generieren UPDATE, die BEGIN TRANfür Zeilenbereiche funktionieren. In diesem Fall benötigen Sie das / 'COMMIT` seitdem nicht mehr Jede Anweisung kann alle 4500 Zeilen auf einmal verarbeiten:
conn.Open();
using (SqlCommand cmd = new SqlCommand(
@"UPDATE [TestTable]
SET Column5 = NEWID(),
ColumnN = NEWID()
WHERE ID BETWEEN 1 and 4500;
", conn));
Wenn "Ja" zu Nummer 1 steht und es keinen wirklichen Grund dafür gibt, dass diese UPDATEs in .NET generiert werden, können Sie einfach Folgendes tun:
DECLARE @BatchSize INT = 4500, -- this could be an input param for a stored procedure
@RowsAffected INT = 1, -- needed to enter loop
@StartingID INT = 1; -- initial value
WHILE (@RowsAffected > 0)
BEGIN
UPDATE TOP (@BatchSize) tbl
SET tbl.Column5 = NEWID(),
tbl.ColumnN = NEWID()
FROM [TestTable] tbl
WHERE tbl.ID BETWEEN @StartingID AND (@StartingID + @BatchSize - 1);
SET @RowsAffected = @@ROWCOUNT;
SET @StartingID += @BatchSize;
END;
Der obige Code funktioniert nur, wenn die IDWerte nicht spärlich sind oder zumindest wenn die Werte keine größeren Lücken aufweisen als @BatchSize, sodass in jeder Iteration mindestens eine Zeile aktualisiert wird. Dieser Code setzt auch voraus, dass das IDFeld der Clustered Index ist. Diese Annahmen erscheinen angesichts des bereitgestellten Beispielcodes vernünftig.
Wenn die IDWerte jedoch große Lücken aufweisen oder wenn das IDFeld nicht der Clustered Index ist, können Sie nur nach Zeilen suchen, die noch keinen Wert haben:
DECLARE @BatchSize INT = 4500, -- this could be an input param for a stored procedure
@RowsAffected INT = 1; -- needed to enter loop
WHILE (@RowsAffected > 0)
BEGIN
UPDATE TOP (@BatchSize) tbl
SET tbl.Column5 = NEWID(),
tbl.ColumnN = NEWID()
FROM [TestTable] tbl
WHERE tbl.Col1 IS NULL;
SET @RowsAffected = @@ROWCOUNT;
END;
ABER , wenn "Nein" zu # 1 und die Werte aus einem guten Grund von .NET stammen, z. B. dass die eindeutigen Werte für jede IDbereits in einer anderen Quelle vorhanden sind, können Sie dies (über meinen ursprünglichen Vorschlag hinaus) noch beschleunigen, indem Sie angeben eine abgeleitete Tabelle:
conn.Open();
using (SqlCommand cmd = new SqlCommand(
@"BEGIN TRAN;
UPDATE tbl
SET tbl.Column5 = tmp.Col1,
tbl.ColumnN = tmp.Col2
FROM [TestTable] tbl
INNER JOIN (VALUES
(1, 'some unique value A', 'some unique value B'),
(2, 'some unique value C', 'some unique value D'),
...
(1000, 'some unique value N1', 'some unique value N2')
) tmp (ID, Col1, Col2)
ON tmp.ID = tbl.ID;
UPDATE tbl
SET tbl.Column5 = tmp.Col1,
tbl.ColumnN = tmp.Col2
FROM [TestTable] tbl
INNER JOIN (VALUES
(1001, 'some unique value A2', 'some unique value B2'),
(1002, 'some unique value C2', 'some unique value D2'),
...
(2000, 'some unique value N3', 'some unique value N4')
) tmp (ID, Col1, Col2)
ON tmp.ID = tbl.ID;
COMMIT TRAN;
", conn));
Ich glaube, dass die Anzahl der Zeilen, über die verbunden werden kann, auf VALUES1000 begrenzt ist. Deshalb habe ich zwei Sätze in einer expliziten Transaktion zusammengefasst. Sie können mit bis zu 4 Sätzen dieser UPDATEs testen , um 4000 pro Transaktion auszuführen und die Grenze von 5000 Sperren zu unterschreiten.