Ja, es klingt nach einem sehr allgemeinen Problem, aber ich konnte es noch nicht sehr eingrenzen.
Ich habe also eine UPDATE-Anweisung in einer SQL-Batchdatei:
UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
B hat 40.000 Datensätze, A hat 4 Millionen Datensätze und sie sind über A.B_ID 1 zu n miteinander verbunden, obwohl zwischen den beiden kein FK besteht.
Grundsätzlich berechne ich ein Feld für Data Mining-Zwecke vor. Obwohl ich den Namen der Tabellen für diese Frage geändert habe, habe ich die Anweisung nicht geändert, es ist wirklich so einfach.
Die Ausführung dauert Stunden, daher habe ich beschlossen, alles abzubrechen. Die Datenbank wurde beschädigt, daher habe ich sie gelöscht, eine Sicherung wiederhergestellt, die ich kurz vor dem Ausführen der Anweisung erstellt habe, und beschlossen, mit einem Cursor näher auf Details einzugehen:
DECLARE CursorB CURSOR FOR SELECT ID FROM B ORDER BY ID DESC -- Descending order
OPEN CursorB
DECLARE @Id INT
FETCH NEXT FROM CursorB INTO @Id
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE @Msg VARCHAR(50) = 'Updating A for B_ID=' + CONVERT(VARCHAR(10), @Id)
RAISERROR(@Msg, 10, 1) WITH NOWAIT
UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
WHERE B.ID = @Id
FETCH NEXT FROM CursorB INTO @Id
END
Jetzt kann ich sehen, dass es mit einer Nachricht mit absteigender ID ausgeführt wird. Es dauert ungefähr 5 Minuten, um von id = 40k auf id = 13 zu gelangen
Und dann, bei ID 13, scheint es aus irgendeinem Grund zu hängen. Die Datenbank hat außer SSMS keine Verbindung zu ihr, aber sie hängt nicht wirklich:
- Die Festplatte läuft ununterbrochen, also macht sie definitiv etwas (ich habe im Process Explorer überprüft, ob es sich tatsächlich um den Prozess sqlserver.exe handelt, der sie verwendet).
Ich habe sp_who2 ausgeführt, die SPID (70) der SUSPENDED-Sitzung gefunden und dann das folgende Skript ausgeführt:
Wählen Sie * aus sys.dm_exec_requests aus. r Join sys.dm_os_tasks t on r.session_id = t.session_id wobei r.session_id = 70
Dies gibt mir den wait_type, der die meiste Zeit PAGEIOLATCH_SH ist, aber manchmal tatsächlich zu WRITE_COMPLETION wechselt, was vermutlich passiert, wenn das Protokoll geleert wird
- Die Protokolldatei, die 1,6 GB groß war, als ich die Datenbank wiederherstellte (und ID 13 erreichte), ist jetzt 3,5 GB groß
Andere vielleicht nützliche Informationen:
- Die Anzahl der Datensätze in Tabelle A für B_ID 13 ist nicht groß (14).
- Meine Kollegin hat nicht das gleiche Problem auf ihrem Computer, mit einer Kopie dieser Datenbank (von vor ein paar Monaten) mit der gleichen Struktur.
- Tabelle A ist mit Abstand die größte Tabelle in der DB
- Es hat mehrere Indizes, und mehrere indizierte Ansichten verwenden es.
- Es gibt keinen anderen Benutzer in der Datenbank, sie ist lokal und wird von keiner Anwendung verwendet.
- Die LDF-Datei ist nicht in der Größe beschränkt.
- Das Wiederherstellungsmodell ist EINFACH, die Kompatibilitätsstufe ist 100
- Procmon gibt mir nicht viele Informationen: sqlserver.exe liest und schreibt viel aus den MDF- und LDF-Dateien.
Ich warte immer noch darauf, dass es fertig ist (es ist 1h30), aber ich hatte gehofft, dass mir vielleicht jemand eine andere Aktion geben würde, mit der ich versuchen könnte, dieses Problem zu beheben.
Bearbeitet: Extrakt aus dem Procmon-Protokoll hinzufügen
15:24:02.0506105 sqlservr.exe 1760 ReadFile C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf SUCCESS Offset: 5,498,732,544, Length: 8,192, I/O Flags: Non-cached, Priority: Normal
15:24:02.0874427 sqlservr.exe 1760 WriteFile C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA.mdf SUCCESS Offset: 6,225,805,312, Length: 16,384, I/O Flags: Non-cached, Write Through, Priority: Normal
15:24:02.0884897 sqlservr.exe 1760 WriteFile C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\TA_1.LDF SUCCESS Offset: 4,589,289,472, Length: 8,388,608, I/O Flags: Non-cached, Write Through, Priority: Normal
Bei Verwendung von DBCC PAGE scheint es, aus Feldern zu lesen und in diese zu schreiben, die wie Tabellen A (oder einem ihrer Indizes) aussehen, aber für andere B_IDs, die 13. Indizes möglicherweise neu erstellen?
Bearbeitet 2: Ausführungsplan
Also habe ich die Abfrage abgebrochen (die Datenbank und ihre Dateien tatsächlich gelöscht und dann wiederhergestellt) und den Ausführungsplan auf Folgendes überprüft:
UPDATE A
SET A.X = B.X
FROM A JOIN B ON A.B_ID = B.ID
WHERE B.ID = 13
Der (geschätzte) Ausführungsplan ist der gleiche wie für jede B.ID und sieht ziemlich einfach aus. Die WHERE-Klausel verwendet eine Indexsuche für einen nicht gruppierten Index von B, die JOIN verwendet eine gruppierte Indexsuche für beide PKs der Tabellen. Die Clustered-Index-Suche für A verwendet Parallelität (x7) und repräsentiert 90% der CPU-Zeit.
Noch wichtiger ist, dass die Abfrage mit der ID 13 tatsächlich sofort ausgeführt wird.
Bearbeitet 3: Indexfragmentierung
Die Struktur der Indizes ist wie folgt:
B hat eine gruppierte PK (nicht das ID-Feld) und einen nicht gruppierten eindeutigen Index. Das erste Feld ist B.ID - dieser zweite Index scheint immer verwendet zu werden.
A hat eine gruppierte PK (Feld nicht verwandt).
Es gibt auch 7 Ansichten zu A (alle enthalten das AX-Feld), jede mit einer eigenen Cluster-PK, und einen anderen Index, der auch das AX-Feld enthält
Die Ansichten werden gefiltert (mit Feldern , die nicht in dieser Gleichung sind), so dass ich bezweifle , dass es irgendeine Art und Weise des UPDATE - A würde verwendet die Ansichten selbst. Sie haben jedoch einen Index, der AX enthält. Wenn Sie also AX ändern, müssen Sie die 7 Ansichten und die 7 Indizes schreiben, die das Feld enthalten.
Obwohl erwartet wird, dass das UPDATE dafür langsamer ist, gibt es keinen Grund, warum eine bestimmte ID so viel länger wäre als die anderen.
Ich habe die Fragmentierung für alle Indizes überprüft, alle lagen bei <0,1%, mit Ausnahme der Sekundärindizes der Ansichten , alle zwischen 25% und 50%. Füllfaktoren für alle Indizes scheinen in Ordnung zu sein, zwischen 90% und 95%.
Ich habe alle Sekundärindizes neu organisiert und mein Skript erneut ausgeführt.
Es wird immer noch gehängt, aber an einem anderen Punkt:
...
(0 row(s) affected)
Updating A for B_ID=14
(4 row(s) affected)
Während zuvor das Nachrichtenprotokoll folgendermaßen aussah:
...
(0 row(s) affected)
Updating A for B_ID=14
(4 row(s) affected)
Updating A for B_ID=13
Das ist komisch, weil es bedeutet, dass es nicht einmal an der gleichen Stelle in der WHILE
Schleife hängt . Der Rest sieht gleich aus: Dieselbe UPDATE-Zeile wartet in sp_who2, der gleiche PAGEIOLATCH_EX-Wartetyp und die gleiche starke HD-Nutzung von sqlserver.exe.
Der nächste Schritt besteht darin, alle Indizes und Ansichten zu löschen und neu zu erstellen, denke ich.
Bearbeitet 4: Löschen und erneutes Erstellen von Indizes
Also habe ich alle indizierten Ansichten gelöscht, die ich in der Tabelle hatte (7 davon, 2 Indizes pro Ansicht, einschließlich der gruppierten). Ich habe das erste Skript (ohne Cursor) ausgeführt und es wurde tatsächlich in 5 Minuten ausgeführt.
Mein Problem ergibt sich also aus der Existenz dieser Indizes.
Ich habe meine Indizes nach dem Ausführen des Updates neu erstellt und es dauerte 16 Minuten.
Jetzt verstehe ich, dass die Wiederherstellung von Indizes einige Zeit in Anspruch nimmt, und ich bin damit einverstanden, dass die gesamte Aufgabe 20 Minuten dauert.
Was ich immer noch nicht verstehe, ist, warum es mehrere Stunden dauert, wenn ich das Update ausführe, ohne zuerst die Indizes zu löschen, aber wenn ich sie zuerst lösche und dann neu erstelle, dauert es 20 Minuten. Sollte es nicht ungefähr so lange dauern?
DBCC PAGE
zu sehen, worauf geschrieben wird.Antworten:
Bearbeiten: Da ich Ihren ursprünglichen Beitrag nicht kommentieren kann, beantworte ich hier Ihre Frage aus Bearbeiten 4. Sie haben 7 Indizes für AX Index ist ein B-Baum , und jede Aktualisierung dieses Felds bewirkt, dass der B-Baum neu ausgeglichen wird. Es ist schneller, diese Indizes von Grund auf neu zu erstellen, als sie jedes Mal neu auszugleichen.
quelle
Eine Sache zu betrachten sind die Systemressourcen (Speicher, Festplatte, CPU) während dieses Prozesses. Ich habe versucht, in einem großen Auftrag 7 Millionen einzelne Zeilen in eine einzelne Tabelle einzufügen, und mein Server hing auf ähnliche Weise wie Sie.
Es stellte sich heraus, dass ich nicht genügend Speicher auf meinem Server hatte, um diesen Masseneinfügejob auszuführen. In solchen Situationen hält SQL den Speicher gerne fest und lässt ihn nicht los ... auch nachdem der Einfügebefehl möglicherweise ausgeführt wurde oder nicht. Je mehr Befehle in großen Jobs verarbeitet werden, desto mehr Speicher wird verbraucht. Durch einen schnellen Neustart wurde der Speicher freigegeben.
Ich würde diesen Prozess von Grund auf neu starten, während Ihr Task-Manager ausgeführt wird. Wenn die Speichernutzung über 75% steigt, steigt die Wahrscheinlichkeit, dass Ihr System / Ihre Prozesse einfrieren, astronomisch sprunghaft an.
Wenn Ihr Speicher / Ihre Ressourcen tatsächlich wie oben angegeben begrenzt sind, können Sie den Prozess in kleinere Teile zerlegen (mit gelegentlichem Neustart bei hoher Speichernutzung) anstelle eines großen Auftrags oder ein Upgrade auf einen 64-Bit-Server mit viel Speicher.
quelle
Das Aktualisierungsszenario ist immer schneller als die Verwendung einer Prozedur.
Da Sie Spalte X aller Zeilen in Tabelle A aktualisieren, müssen Sie zuerst den Index für diese Zeile löschen. Stellen Sie außerdem sicher, dass in dieser Spalte keine Trigger und Einschränkungen aktiv sind.
Das Aktualisieren von Indizes ist ein kostspieliges Geschäft, ebenso wie das Überprüfen von Einschränkungen und das Ausführen von Triggern auf Zeilenebene, die eine Suche in anderen Daten durchführen.
quelle