it-swarm.com.de

Warum sollte eine Tabelle mit einem Clustered Columnstore-Index viele offene Zeilengruppen haben?

Ich hatte gestern einige Leistungsprobleme mit einer Abfrage und bei weiteren Untersuchungen stellte ich fest, dass es sich meiner Meinung nach um ein seltsames Verhalten mit einem Clustered Columnstore-Index handelt, dem ich auf den Grund gehen möchte.

Der Tisch ist

CREATE TABLE [dbo].[NetworkVisits](
    [SiteId] [int] NOT NULL,
    [AccountId] [int] NOT NULL,
    [CreationDate] [date] NOT NULL,
    [UserHistoryId] [int] NOT NULL
)

mit dem Index:

CREATE CLUSTERED COLUMNSTORE INDEX [CCI_NetworkVisits] 
   ON [dbo].[NetworkVisits] WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) ON [PRIMARY]

Die Tabelle enthält derzeit 1,3 Milliarden Zeilen, und wir fügen ständig neue Zeilen ein. Wenn ich ständig sage, meine ich die ganze Zeit. Es ist ein stetiger Strom, bei dem jeweils eine Zeile in die Tabelle eingefügt wird.

Insert Into NetworkVisits (SiteId, AccountId, CreationDate, UserHistoryId)
Values (@SiteId, @AccountId, @CreationDate, @UserHistoryId)

Ausführungsplan hier

Ich habe auch einen geplanten Job, der alle 4 Stunden ausgeführt wird, um doppelte Zeilen aus der Tabelle zu löschen. Die Abfrage lautet:

With NetworkVisitsRows
  As (Select SiteId, UserHistoryId, Row_Number() Over (Partition By SiteId, UserHistoryId
                                    Order By CreationDate Asc) RowNum
        From NetworkVisits
       Where CreationDate > GETUTCDATE() - 30)
DELETE
FROM NetworkVisitsRows
WHERE RowNum > 1
Option (MaxDop 48)

Der Ausführungsplan wurde eingefügt hier .

Als ich mich mit dem Problem befasste, bemerkte ich, dass die Tabelle NetworkVisits ungefähr 2000 Zeilengruppen enthielt, von denen sich ungefähr 800 in einem offenen Zustand befanden und nicht annähernd das maximal zulässige (1048576). Hier ist eine kleine Auswahl von dem, was ich gesehen habe:

(enter image description here

Ich habe eine Reorganisation für den Index durchgeführt, bei der alle bis auf eine Zeilengruppe komprimiert wurden. Heute Morgen habe ich erneut überprüft, und wir haben wieder mehrere offene Zeilengruppen - die, die gestern nach der Reorganisation erstellt wurde, und dann jeweils ungefähr drei weitere, die ungefähr zum Zeitpunkt des Löschens erstellt wurden Job lief:

TableName       IndexName           type_desc               state_desc  total_rows  deleted_rows    created_time
NetworkVisits   CCI_NetworkVisits   CLUSTERED COLUMNSTORE   OPEN        36754       0               2019-12-18 18:30:54.217
NetworkVisits   CCI_NetworkVisits   CLUSTERED COLUMNSTORE   OPEN        172103      0               2019-12-18 20:02:06.547
NetworkVisits   CCI_NetworkVisits   CLUSTERED COLUMNSTORE   OPEN        132628      0               2019-12-19 04:03:10.713
NetworkVisits   CCI_NetworkVisits   CLUSTERED COLUMNSTORE   OPEN        397718      0               2019-12-19 08:02:13.063

Ich versuche herauszufinden, was möglicherweise dazu führen kann, dass neue Zeilengruppen erstellt werden, anstatt die vorhandene zu verwenden.

Ist es möglicherweise Speicherdruck oder Konflikt zwischen dem Einfügen und dem Löschen? Ist dieses Verhalten irgendwo dokumentiert?

Auf diesem Server wird SQL Server 2017 CU 16 Enterprise Edition ausgeführt.

Das INSERT ist MAXDOP 0, das DELETE ist MAXDOP 48. Die einzigen geschlossenen Zeilengruppen sind die aus dem anfänglichen BULKLOAD und dann dem REORG_FORCED das habe ich gestern gemacht, also die trimmgründe in sys.dm_db_column_store_row_group_physical_stats sind REORG und NO_TRIM beziehungsweise. Darüber hinaus gibt es keine geschlossenen Zeilengruppen. Für diese Tabelle werden keine Updates ausgeführt. In der Einfügeanweisung werden durchschnittlich 520 Ausführungen pro Minute ausgeführt. Es gibt keine Partitionierung in der Tabelle.

Mir sind Rieseleinsätze bekannt. Wir machen dasselbe an anderer Stelle und haben nicht das gleiche Problem mit mehreren offenen Zeilengruppen. Unser Verdacht ist, dass es mit dem Löschen zu tun hat. Jede neu erstellte Zeilengruppe befindet sich ungefähr zum Zeitpunkt des geplanten Löschauftrags. Es gibt nur zwei Delta-Speicher, in denen gelöschte Zeilen angezeigt werden. Wir löschen nicht wirklich viele Daten aus dieser Tabelle, zum Beispiel während einer Ausführung gestern wurden 266 Zeilen gelöscht.

20
Taryn

Warum sollte eine Tabelle mit einem Clustered Columnstore-Index viele offene Zeilengruppen haben?

Es gibt viele verschiedene Szenarien, die dies verursachen können. Ich werde die allgemeine Frage weiter beantworten, um Ihr spezifisches Szenario anzusprechen, von dem ich denke, dass es das ist, was Sie wollen.

Ist es möglicherweise Speicherdruck oder Konflikt zwischen dem Einfügen und dem Löschen?

Es ist kein Gedächtnisdruck. SQL Server fordert beim Einfügen einer einzelnen Zeile in eine Spaltenspeichertabelle keine Speicherzuweisung an. Es ist bekannt, dass die Zeile in eine Delta-Zeilengruppe eingefügt wird, sodass die Speicherzuweisung nicht erforderlich ist. Es ist möglich, mehr Delta-Zeilengruppen zu erhalten, als man erwarten könnte, wenn mehr als 102399 Zeilen pro INSERT -Anweisung eingefügt werden und das festgelegte Zeitlimit für die Speichergewährung von 25 Sekunden erreicht wird. Dieses Speicherdruckszenario dient jedoch zum Massenladen und nicht zum Erhaltungsladen.

Inkompatible Sperren zwischen DELETE und INSERT sind eine plausible Erklärung für das, was Sie mit Ihrer Tabelle sehen. Denken Sie daran, dass ich in der Produktion keine Rieseleinfügungen mache, aber die aktuelle Sperrimplementierung zum Löschen von Zeilen aus einer Delta-Zeilengruppe scheint eine UIX-Sperre zu erfordern. Sie können dies mit einer einfachen Demo sehen:

Werfen Sie in der ersten Sitzung einige Zeilen in den Delta-Speicher:

DROP TABLE IF EXISTS dbo.LAMAK;

CREATE TABLE dbo.LAMAK (
ID INT NOT NULL,
INDEX C CLUSTERED COLUMNSTORE
);

INSERT INTO dbo.LAMAK
SELECT TOP (64000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

Löschen Sie eine Zeile in der zweiten Sitzung, übernehmen Sie die Änderung jedoch noch nicht:

BEGIN TRANSACTION;

DELETE FROM dbo.LAMAK WHERE ID = 1;

Sperren für DELETE per sp_whoisactive:

<Lock resource_type="HOBT" request_mode="UIX" request_status="GRANT" request_count="1" />
<Lock resource_type="KEY" request_mode="X" request_status="GRANT" request_count="1" />
<Lock resource_type="OBJECT" request_mode="IX" request_status="GRANT" request_count="1" />
<Lock resource_type="OBJECT.INDEX_OPERATION" request_mode="S" request_status="GRANT" request_count="1" />
<Lock resource_type="PAGE" page_type="*" request_mode="IX" request_status="GRANT" request_count="1" />
<Lock resource_type="ROWGROUP" resource_description="ROWGROUP: 5:100000004080000:0" request_mode="UIX" request_status="GRANT" request_count="1" />

Fügen Sie in der ersten Sitzung eine neue Zeile ein:

INSERT INTO dbo.LAMAK
VALUES (0);

Übernehmen Sie die Änderungen in der zweiten Sitzung und überprüfen Sie sys.dm_db_column_store_row_group_physical_stats:

(dr dmv

Eine neue Zeilengruppe wurde erstellt, da die Einfügung eine IX-Sperre für die geänderte Zeilengruppe anfordert. Eine IX-Sperre ist nicht mit einer UIX-Sperre kompatibel. Dies scheint die aktuelle interne Implementierung zu sein, und Microsoft wird sie möglicherweise im Laufe der Zeit ändern.

In Bezug auf die Vorgehensweise bei der Behebung sollten Sie überlegen, wie diese Daten verwendet werden. Ist es wichtig, dass die Daten so komprimiert wie möglich sind? Benötigen Sie eine gute Eliminierung von Zeilengruppen in der Spalte [CreationDate]? Wäre es in Ordnung, wenn neue Daten einige Stunden lang nicht in der Tabelle angezeigt würden? Würden Endbenutzer es vorziehen, wenn Duplikate nie in der Tabelle angezeigt würden, anstatt bis zu vier Stunden darin zu existieren?

Die Antworten auf all diese Fragen bestimmen den richtigen Weg zur Lösung des Problems. Hier sind einige Optionen:

  1. Führen Sie einmal täglich ein REORGANIZE mit der Option COMPRESS_ALL_ROW_GROUPS = ON Für den Spaltenspeicher aus. Im Durchschnitt bedeutet dies, dass die Tabelle eine Million Zeilen im Delta-Speicher nicht überschreitet. Dies ist eine gute Option, wenn Sie nicht die bestmögliche Komprimierung benötigen, nicht die beste Zeilengruppeneliminierung in der Spalte [CreationDate] Benötigen und den Status Quo des Löschens doppelter Zeilen alle vier Stunden beibehalten möchten .

  2. Teilen Sie die Anweisungen DELETE in separate Anweisungen INSERT und DELETE auf. Fügen Sie die zu löschenden Zeilen als ersten Schritt in eine temporäre Tabelle ein und löschen Sie sie mit TABLOCKX in der zweiten Abfrage. Dies muss nicht in einer Transaktion erfolgen, die auf Ihrem Datenlademuster (nur Einfügungen) und der Methode basiert, mit der Sie Duplikate suchen und entfernen. Das Löschen einiger hundert Zeilen sollte sehr schnell erfolgen, wobei die Spalte [CreationDate] Gut entfernt wird, die Sie mit diesem Ansatz erhalten. Der Vorteil dieses Ansatzes besteht darin, dass Ihre komprimierten Zeilengruppen enge Bereiche für [CreationDate] Haben, vorausgesetzt, das Datum für diese Spalte ist das aktuelle Datum. Der Nachteil ist, dass Ihre Rieseleinsätze möglicherweise einige Sekunden lang nicht ausgeführt werden können.

  3. Schreiben Sie neue Daten in eine Staging-Tabelle und leeren Sie sie alle X Minuten in den Spaltenspeicher. Als Teil des Flush-Prozesses können Sie das Einfügen von Duplikaten überspringen, sodass die Haupttabelle niemals Duplikate enthält. Der andere Vorteil besteht darin, dass Sie steuern, wie oft die Daten gelöscht werden, damit Sie Zeilengruppen mit der gewünschten Qualität erhalten. Der Nachteil ist, dass neue Daten nicht mehr in der Tabelle [dbo].[NetworkVisits] Angezeigt werden. Sie könnten eine Ansicht versuchen, die die Tabellen kombiniert, aber dann müssen Sie darauf achten, dass Ihr Prozess zum Löschen von Daten zu einer konsistenten Ansicht der Daten für Endbenutzer führt (Sie möchten nicht, dass Zeilen während des Vorgangs verschwinden oder zweimal angezeigt werden Prozess).

Schließlich stimme ich anderen Antworten nicht zu, dass eine Neugestaltung der Tabelle in Betracht gezogen werden sollte. Sie fügen durchschnittlich nur 9 Zeilen pro Sekunde in die Tabelle ein, was einfach keine hohe Rate ist. Eine einzelne Sitzung kann 1500 Singleton-Einfügungen pro Sekunde in eine Spaltenspeichertabelle mit sechs Spalten ausführen. Möglicherweise möchten Sie das Tabellendesign ändern, sobald Sie Zahlen in der Umgebung sehen.

5
Joe Obbish

Mit konstanten Rieseleinsätzen kann es sehr gut zu zahlreichen offenen Deltastore-Reihengruppen kommen. Der Grund dafür ist, dass beim Start einer Einfügung eine neue Zeilengruppe erstellt wird, wenn alle vorhandenen gesperrt sind. Von Treppe zu Columnstore-Indizes Stufe 5: Hinzufügen neuer Daten zu Columnstore-Indizes

Jede Einfügung von 102.399 oder weniger Zeilen wird als "Rieseleinfügung" betrachtet. Diese Zeilen werden einem offenen Deltastore hinzugefügt, wenn einer verfügbar (und nicht gesperrt) ist, oder es wird eine neue Deltastore-Zeilengruppe für sie erstellt.

Im Allgemeinen ist das Columnstore-Indexdesign für Masseneinfügungen optimiert. Wenn Sie Trickle-Einfügungen verwenden, müssen Sie die Neuorganisation regelmäßig ausführen.

Eine weitere in der Microsoft-Dokumentation empfohlene Option besteht darin, in eine Staging-Tabelle (Heap) zu gelangen. Wenn diese mehr als 102.400 Zeilen umfasst, fügen Sie diese Zeilen in den Columstore-Index ein. Siehe Columnstore-Indizes - Anleitung zum Laden von Daten

In jedem Fall wird nach dem Löschen vieler Daten eine Neuorganisation für einen Columnstore-Index empfohlen, damit die Daten tatsächlich gelöscht werden und die resultierenden Deltastore-Zeilengruppen bereinigt werden.

12
Tony Hinkle

Dies sieht aus wie ein Edge-Fall für die Clustered Columnstore-Indizes, und am Ende ist dies eher ein HTAP Szenario unter aktuellen Microsoft-Überlegungen - was bedeutet, dass ein NCCI ein wäre bessere Lösung. Ja, ich stelle mir vor, dass das Verlieren dieser Columnstore-Komprimierung für den Clustered-Index in Bezug auf den Speicher wirklich schlecht wäre, aber wenn Ihr Hauptspeicher Delta-Stores sind, werden Sie ohnehin nicht komprimiert ausgeführt.

Ebenfalls:

  • Was passiert, wenn Sie den DOP der DELETE-Anweisungen senken?
  • Haben Sie versucht, sekundäre Rowstore Nonclustered-Indizes hinzuzufügen, um die Blockierung zu verringern (ja, dies hat Auswirkungen auf die Komprimierungsqualität).
3
Niko Neugebuer