Optimieren von Abfragen für mehr als 25 Millionen Zeilen


11

Ich verwende MS SQL und muss mehrere Abfragen in derselben Tabelle nach unterschiedlichen Kriterien ausführen. Zuerst habe ich jede Abfrage in der Originaltabelle ausgeführt, obwohl alle eine gewisse Filterung aufweisen (dh Datum, Status). Dies hat viel Zeit in Anspruch genommen (ca. 2 Minuten).

Es gibt Duplikate in Datenzeilen, und alle Indizes sind NICHT CLUSTERED. Ich interessiere mich nur für 4 Spalten für meine Kriterien und das Ergebnis sollte nur die Anzahl für alle Abfragen ausgeben.

Spalten benötigt: TABLE, FIELD, AFTER, DATE, und ein Index von auf jedem ist DATEund TABLE.

Nachdem ich eine temporäre Tabelle mit nur den Feldern erstellt hatte, die ich benötige, ging es auf 1:40 Minuten zurück, was immer noch sehr schlecht ist.

CREATE TABLE #TEMP
(
    TABLE VARCHAR(30) NULL,
    FIELD VARCHAR(30) NULL,
    AFTER VARCHAR(1000) NULL,
    DATE DATETIME,
    SORT_ID INT IDENTITY(1,1)
)
CREATE CLUSTERED INDEX IX_ADT ON #TEMP(SORT_ID)

INSERT INTO #TEMP (TABLE, FIELD, AFTER, DATE)
    SELECT TABLE, FIELD, AFTER, DATE 
    FROM mytbl WITH (NOLOCK)
    WHERE TABLE = 'OTB' AND
    FIELD = 'STATUS'

Führen Sie dies aus -> (216598 betroffene Zeile (n))

Da nicht alle Abfragen vom Datumsbereich abhängen, habe ich ihn nicht in die Abfrage aufgenommen. Das Problem ist, dass das Einfügen nur deutlich länger als 1 Minute dauert . Der obige Einsatz dauerte 1:19 Minuten

Ich möchte so etwas für mehrere Abfragen ausführen:

SELECT COUNT(*) AS COUNT
FROM #TEMP
WHERE AFTER = 'R' AND
DATE >= '2014-01-01' AND
DATE <= '2015-01-01'

Es ist ein Problem mit dem Einfügen mehr als das der Auswahl, aber die Temperatur hat viel weniger Zeilen als die ursprüngliche Tabelle, was besser sein könnte, als die Tabelle mehrmals durchzugehen.

Wie kann ich das optimieren?

BEARBEITEN

Ich habe die Sortier-ID entfernt und dachte, das Problem liege hauptsächlich in der Auswahl und nicht in der Einfügung. Es war eine Vermutung.

Ich kann für keinen Index ein eindeutiges Feld erstellen, da es keine eindeutigen Felder oder Zeilen gibt.

Ich verwende SQL Server 2012.

Tabelleninfo : Es ist ein Haufen und hat die folgende Speicherplatznutzung:

name    rows        reserved    data        index_size  unused
mytbl   24869658    9204568 KB  3017952 KB  5816232 KB  370384 KB

@MikaelEriksson Ich kann Produktionstabellen nicht ändern ..
Atieh

Wenn die Abfragen, die Sie optimieren möchten, die Form haben SELECT COUNT(*) AS COUNT FROM original_table WHERE AFTER = 'R' AND DATE >= '2014-01-01' AND DATE < '2015-01-01', warum versuchen Sie nicht, jede (Abfrage) separat zu optimieren? Dürfen Sie der Tabelle keine Indizes hinzufügen?
Ypercubeᵀᴹ

2
Sie müssen feststellen, warum es langsam ist. Wird es blockiert? Wartet es darauf, dass Tempdb wächst? Ist der Ausführungsplan miserabel? Niemand kann "meine Anfrage ist langsam" ohne weitere Details beheben ...
Aaron Bertrand

3
Nun, scheint mir eine verlorene Sache zu sein ( "Ich darf nichts optimieren, also lassen Sie uns jedes Mal, wenn wir einige Abfragen ausführen müssen, einfach 200.000 Zeilen in eine temporäre Tabelle verschieben" ). Aber Sie könnten die TABLEund FIELDSpalten aus der #tempTabelle entfernen (alle Zeilen haben schließlich TABLE = 'OTB' AND FIELD = 'STATUS'für die spezifische temporäre Tabelle.)
ypercubeᵀᴹ

2
Ich habe um eine Bearbeitung und Verbesserungen gebeten, indem ich einen detaillierten (und höflichen) Kommentar hinzugefügt habe. Dafür sind Kommentare da. Sie sollten Ihre Frage auch mit der von Ihnen verwendeten Version von SQL Server kennzeichnen (z. B. SQL Server 2014). DDL für die Tabelle kann ebenfalls hilfreich sein ( CREATE TABLEAnweisung). Die Abwahl erfolgte, weil die Frage nicht klar war.
Paul White 9

Antworten:


12

Die Frage betrifft hauptsächlich die Optimierung der select-Anweisung:

SELECT [TABLE], [FIELD], [AFTER], [DATE]
FROM mytbl WITH (NOLOCK)
WHERE [TABLE] = 'OTB' AND
[FIELD] = 'STATUS'

Entfernen der redundanten Projektionen und Hinzufügen des vermuteten dboSchemas:

SELECT [AFTER], [DATE] 
FROM dbo.mytbl WITH (NOLOCK)
WHERE [TABLE] = 'OTB'
AND FIELD = 'STATUS';

Ohne einen Index wie ([TABLE],[FIELD]) INCLUDE ([AFTER],[DATE])SQL Server gibt es zwei Hauptoptionen:

  1. Scannen Sie den Heap vollständig (3 GB +). oder
  2. Suchen Sie nach übereinstimmenden Zeilen [TABLE] = 'OTB'und [FIELD] = 'STATUS'(mit IDX6) und führen Sie dann eine Heap-Suche (RID) pro Zeile durch , um die Spalten [AFTER]und abzurufen [DATE].

Ob der Optimierer einen Heap-Scan oder eine Indexsuche mit RID-Suche wählt, hängt von der geschätzten Selektivität der Prädikate [TABLE] = 'OTB'und ab [FIELD] = 'STATUS'. Überprüfen Sie, ob die geschätzte Anzahl der Zeilen aus der Suche mit der Realität übereinstimmt. Wenn nicht, aktualisieren Sie Ihre Statistiken. Testen Sie die Abfrage mit einem Tabellenhinweis, der die Verwendung des Index erzwingt, wenn diese Bedingung einigermaßen selektiv ist . Wenn das Optimierungsprogramm derzeit die Indexsuche auswählt, testen Sie die Leistung mit einem INDEX(0)oder einem FORCESCANHinweis, um den Heap zu scannen.

Darüber hinaus können Sie versuchen, den Scan des Heaps ein wenig zu verbessern, indem Sie einen Teil des nicht genutzten Speicherplatzes (370 MB) entfernen. In SQL Server 2008 kann dies durch Neuerstellen des Heaps erfolgen. Nicht genutzter Speicherplatz in Heaps resultiert häufig aus Löschvorgängen, bei denen keine Tabellensperre vorgenommen wurde (ohne Tabellensperre werden leere Seiten nicht von einem Heap freigegeben). Tabellen, bei denen häufig gelöscht wird, werden aus diesem Grund häufig besser als Clustertabelle gespeichert.

Die Leistung des Heap-Scans hängt davon ab, wie viel der Tabelle im Speicher gespeichert ist, wie viel von der Festplatte gelesen werden muss, wie voll die Seiten sind, wie schnell der dauerhafte Speicher ist, ob der Scan E / A- oder CPU-gebunden ist ( Parallelität kann helfen).

Wenn die Leistung nach einer Untersuchung aller oben genannten Punkte immer noch nicht akzeptabel ist, versuchen Sie, einen neuen Index zu finden. Wenn in Ihrer Version von SQL Server verfügbar, wäre ein möglicher gefilterter Index für die angegebene Abfrage:

CREATE INDEX index_name
ON dbo.mytbl ([DATE],[AFTER])
WHERE [TABLE] = 'OTB'
AND [FIELD] = 'STATUS';

Berücksichtigen Sie auch die Indexkomprimierung, sofern diese verfügbar und vorteilhaft ist. Ohne einen neuen Index können Sie relativ wenig tun, um die Leistung der angegebenen Abfrage zu verbessern.


Sorry Paul, da ist : IDX6 nonclustered located on PRIMARY TABLE, FIELD. Vielleicht würde dies die Dinge ändern, die Sie erwähnt haben?
Atieh

6

Ich denke, es gibt einen Grund, die Indizes hier zu ändern, weil:

  • Sie haben eine Aufgabe zu erledigen (diese mehreren Abfragen)
  • Data Warehouse-Volumen (mehr als 25 Millionen Zeilen) und
  • ein Leistungsproblem.

Dies wäre auch ein guter Anwendungsfall für nicht in Clustern eingeführte Columnstore-Indizes, die in SQL Server 2012 eingeführt wurden, dh einige Spalten in einer großen Tabelle mit vielen Spalten zusammenfassen / aggregieren.

Obwohl diese Indizes den Nebeneffekt haben, dass die Tabelle schreibgeschützt ist (mit Ausnahme der Partitionsumschaltung), können sie die Leistung aggregierter Abfragen unter den richtigen Bedingungen verändern. Der schreibgeschützte Aspekt kann verwaltet werden, indem der Index oder einfache Partitionswechseldaten in der Tabelle gelöscht und neu erstellt werden.

Ich habe einen einfachen Prüfstand eingerichtet, um Ihr Setup nachzuahmen, und eine gute Leistungsverbesserung festgestellt:

USE tempdb
GO

SET NOCOUNT ON
GO

-- Create a large table
IF OBJECT_ID('dbo.largeTable') IS NOT NULL
DROP TABLE dbo.largeTable
GO
CREATE TABLE dbo.largeTable ( 

    [TABLE] VARCHAR(30) NULL,
    FIELD VARCHAR(30) NULL,
    [AFTER] VARCHAR(1000) NULL,
    [DATE] DATETIME,
    SORT_ID INT IDENTITY(1,1),

    pad VARCHAR(100) DEFAULT REPLICATE( '$', 100 )
)
GO

-- Populate table
;WITH cte AS (
SELECT TOP 100000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT INTO dbo.largeTable ( [TABLE], FIELD, [AFTER], [DATE] )
SELECT 
    x.tableName, 
    y.field,
    z.[after],
    DATEADD( day, rn % 1111, '1 Jan 2012' )
FROM cte c
    CROSS JOIN ( VALUES ( 'OTB' ), ( 'AAA' ), ( 'BBB' ), ( 'CCCC' ) ) x ( tableName )
    CROSS JOIN ( VALUES ( 'STATUS' ), ( 'TIME' ), ( 'POWER' ) ) y ( field )
    CROSS JOIN ( VALUES ( 'R' ), ( 'X' ), ( 'Z' ), ( 'A' ) ) z ( [after] )

CHECKPOINT

GO 5

EXEC sp_spaceused 'dbo.largeTable'
GO

SELECT MIN([DATE]) xmin, MAX([DATE]) xmax, FORMAT( COUNT(*), '#,#' ) records
FROM dbo.largeTable
GO

-- Optionally clear cache for more comparable results; DO NOT RUN ON PRODUCTION SYSTEM!!
--DBCC DROPCLEANBUFFERS
--DBCC FREEPROCCACHE
--GO

DECLARE @startDate DATETIME2 = SYSUTCDATETIME()

SELECT COUNT(*) AS COUNT
FROM dbo.largeTable
WHERE [AFTER] = 'R' 
  AND [DATE] >= '2014-01-01' 
  AND [DATE] <= '2015-01-01'

SELECT DATEDIFF( millisecond, @startDate, SYSUTCDATETIME() ) diff1
GO

-- Add the non-clustered columnstore
CREATE NONCLUSTERED COLUMNSTORE INDEX _cs ON dbo.largeTable ( [TABLE], FIELD, [AFTER], [DATE] )
GO

-- Optionally clear cache for more comparable results; DO NOT RUN ON PRODUCTION SYSTEM!!
--DBCC DROPCLEANBUFFERS
--DBCC FREEPROCCACHE
--GO

-- Check query again
DECLARE @startDate DATETIME2 = SYSUTCDATETIME()

SELECT COUNT(*) AS COUNT
FROM dbo.largeTable
WHERE [AFTER] = 'R' 
  AND [DATE] >= '2014-01-01' 
  AND [DATE] <= '2015-01-01'

SELECT DATEDIFF( millisecond, @startDate, SYSUTCDATETIME() ) diff2
GO

Meine Ergebnisse, 6 Sekunden v 0,08 Sekunden:

Geben Sie hier die Bildbeschreibung ein

Versuchen Sie zusammenfassend, mit Ihrem Chef einen Fall zu erstellen, in dem die Indizes geändert werden, oder erstellen Sie zumindest einen Prozess über Nacht, bei dem diese Datensätze in eine schreibgeschützte Berichtstabelle / Datenbank umgewandelt werden, in der Sie Ihre Arbeit erledigen können, und fügen Sie eine Indizierung hinzu geeignet für diese Arbeitsbelastung.

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.