SQL-Update-Sättigung dauert sehr lange / hohe Festplattenauslastung für Stunden


8

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 WHILESchleife 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?


1
Gibt es etwas im SQL Server-Fehlerprotokoll? Auch von procmon, was sind die Offsets in der Datei, in die geschrieben wird? Sie können durch 8.192 teilen, um die Seite zu erhalten, und dann verwenden, um DBCC PAGEzu sehen, worauf geschrieben wird.
Martin Smith

3,5 GB scheinen die maximale RAM-Größe zu sein, die eine 32-Bit-Windows-Arbeit verarbeiten kann. Gefahr?
tschmit007

@ MartinSmith Es gibt absolut nichts, seit ich in den SSMS SQL Server-Protokollen wiederhergestellt habe und auch nichts im Windows-Ereignisprotokoll
GFK

Wie sehen Ihre Indizes in Tabelle A aus (welche Spalten usw.)? Sind sie fragmentiert?
Stuart Ainsworth

@ tschmit007 Seine SQL 2008 R2 x64 Dev Edition unter Win Server 2008 R2 x64. Es ist eine VM, die selbst auf Hyper-V ausgeführt wird (Host ist auch 2008 R2 x64). Die VM verfügt über 4,2 GB physischen Speicher, der von 5 GB verwendet wird, und 4,6 GB Commit von maximal 10 GB. Der Host verfügt über 7,2 GB physischen Speicher, der von 8 GB verwendet wird, und 7,8 GB Commit von maximal 16 GB. Beide Maschinen sind aufgrund der HD-Nutzung langsamer, aber nicht verstopft.
GFK

Antworten:


0
  1. Halten Sie sich an den Befehl UPDATE. CURSOR ist langsamer für das, was Sie versuchen zu tun.
  2. Löschen / Deaktivieren Sie alle Indizes, einschließlich der Indizes für indizierte Ansichten. Wenn Sie einen Fremdschlüssel in AX haben, lassen Sie ihn fallen.
  3. Erstellen Sie einen Index, der nur A.B_ID und einen weiteren für B.ID enthält.
  4. Obwohl Sie das Simple Recovery-Modell verwenden, befindet sich die letzte Transaktion immer im Transaktionsprotokoll, bevor sie auf die Festplatte geschrieben wird. Aus diesem Grund müssen Sie Ihr Transaktionsprotokoll vorab erweitern und so einstellen, dass es um einen größeren Betrag (z. B. 100 MB) erweitert wird.
  5. Stellen Sie außerdem das Wachstum von Datendateien auf einen größeren Wert ein.
  6. Stellen Sie sicher, dass Sie über genügend Speicherplatz für das weitere Wachstum von Protokoll- und Datendateien verfügen.
  7. Wenn die Aktualisierung abgeschlossen ist, erstellen / aktivieren Sie Indizes neu, die Sie in Schritt 2 gelöscht / deaktiviert haben.
  8. Wenn Sie sie nicht mehr benötigen, löschen Sie die in Schritt 3 erstellten Indizes.

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.


Zu Punkt 1 siehe meine Antwort auf ik_zelf. Der Cursor ist aus Untersuchungsgründen da und hat nicht so viel Einfluss. Ich werde den Rest Ihrer Vorschläge umsetzen. Ich denke, es ist alles, was ich noch tun muss. Wenn es funktioniert, werde ich immer noch ohne Erklärung bleiben, was jetzt passiert ...
GFK

Sie können DDL für Ihre Tabellen veröffentlichen (einschließlich aller Indizes, Einschränkungen usw.). Vielleicht verlangsamt etwas Ihre Leistung und das fehlt Ihnen.
Bojan

1
Indizes löschen / Indizes aktualisieren / neu erstellen funktioniert, und obwohl ich es vorziehen würde, nichts so drastisches tun zu müssen, sehe ich keine Wahl. Vielen Dank!
GFK

0

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.


0

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.


Ich denke nicht, dass das der Punkt ist. Mir ist klar, dass die Aktualisierung indizierter Datensätze einige Zeit in Anspruch nimmt, und ich weiß, dass ein Teil der dafür erforderlichen Zeit insgesamt darauf zurückzuführen ist. Aber ich erwarte dies und bin damit einverstanden: Wie gesagt, das Aktualisieren von 99% der Zeilen dauert 5 Minuten (sogar mit dem Cursor), aber aus irgendeinem Grund dauert eine Zeile (und nicht immer dieselbe) 5 Stunden. Was mich beunruhigt, ist dieses besondere Verhalten.
GFK

Sperren sind kein Problem, das Sie gesagt haben ... wie wäre es mit einer Auslastung des Dateisystems, die 90% oder mehr erreicht?
ik_zelf

Nein, es ist 31 GB frei von 120 GB, also denke ich, dass es in Ordnung ist
GFK

Was passiert, wenn Sie versuchen, die Tabelle wie create table a_copy als select * from a zu kopieren?
ik_zelf
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.