Beschleunigen Sie die Anzahl (*) auf großen Tabellen

8

Wir verwenden eine Vendor App, die unter SQL Server Enterprise ausgeführt wird, und es ist ziemlich ärgerlich, COUNTAnweisungen in der Items-Tabelle auszuführen, während die meisten Finanzdokumente (Bestellungen, Rechnungen usw.) verarbeitet werden.

Z.B SELECT COUNT('A') FROM [dbo].[Items] T0

Ich bin mir sicher, dass das normalerweise in Ordnung wäre, aber es gibt über 6 Millionen Datensätze, und es dauert ungefähr 400 ms, um sie alle zu zählen. Dies kann einen wesentlichen Teil der gesamten Verarbeitungszeit ausmachen.

Die Tabelle enthält bereits einen extrem engen NonClustered-Index (tinyint plus Clustered Key), den SQL beim Tabellenscan verwendet. Ich denke, wir können diesbezüglich keine besseren Ergebnisse erzielen.

Mir sind einige Lösungen bekannt, die wir nach Möglichkeit vermeiden möchten:

Haben wir andere Möglichkeiten, dies zu beschleunigen?

Hier ist eine Übersicht über das Setup: https://gist.github.com/elvishfiend/5094f120b14f8ecfb325623edcb5f3eb

Zac Faragher
quelle
Sie können es wahrscheinlich nicht ändern, da es sich um eine Vendor-App handelt, aber Aaron hat einen Blog-Beitrag zum Zählen von Zeilen in Tabellen: sqlperformance.com/2014/10/t-sql-queries/…
Greg

Antworten:

14

Die indizierte Ansicht sollte bei optimaler Implementierung zu den schnellsten Optionen mit dem geringsten Wartungsaufwand gehören .

Änderungen sind inkrementell (Deltas), wie ich in der Wartung der indizierten Ansicht in Ausführungsplänen ausführlich erläutere (eine vollständige Nachzählung wird nicht bei jeder Aktualisierung der Basistabelle durchgeführt). Sie müssen jedoch sicherstellen, dass die Delta-Aktualisierungsteile des Ausführungsplans über effiziente Zugriffsmethoden verfügen (wie jede Abfrage).

Es ist normalerweise recht einfach, einen fehlenden Index aus dem INSERT/UPDATE/DELETEAusführungsplan zu identifizieren . Vielleicht könnten Sie Ihrer Frage einen veranschaulichenden (tatsächlichen) Ausführungsplan nach der Ausführung hinzufügen.

Der automatische Abgleich von Abfragetext mit einer indizierten Ansicht ist nur in Enterprise Edition (und entsprechenden Entsprechungen) verfügbar. In anderen Editionen müssen Sie den WITH (NOEXPAND)Tabellenhinweis verwenden. Es gibt auch gute Gründe , NOEXPANDauch auf Enterprise Edition zu verwenden.

Zum Demo-Code: Stellen Sie sicher, dass Sie den Hinweis mit angeben WITH (NOEXPAND). Die Art und Weise, wie Sie es geschrieben haben, NOEXPANDwird als Alias ​​analysiert. Beachten Sie auch, dass nur materialisierte (indizierte) Ansichten einen NOEXPANDHinweis haben können.

Wenn Sie keinen Hinweis direkt hinzufügen können, ist dies eine hervorragende Verwendung eines Planleitfadens. Ein Planleitfaden kann auch verwendet werden, um sicherzustellen, dass eine Abfrage, die einer indizierten Ansicht entspricht (ohne sie explizit zu benennen), tatsächlich die indizierte Ansicht verwendet.

Denken Sie daran, dass NOEXPANDSQL Server ohne eine materialisierte (indizierte) Ansicht die Ansichtsdefinition immer zu Beginn der Planerstellung erweitert. Die Enterprise Edition kann (Teile) einer Abfrage mit einer indizierten Ansicht abgleichen (oder auch nicht), abhängig von der Bewertung der Kosten für jede Option.

Verwandte Fragen und Antworten:

Paul White 9
quelle
6

Wenn Sie mit SQL Server 2012 nicht weiterkommen, können Sie versuchen, einen Index nur für den Clustered-Indexschlüssel zu erstellen. Es ist möglicherweise etwas kleiner als ein Index für eine TINYINTSpalte. Sie können auch versuchen, Ihrem Index eine Seitenkomprimierung hinzuzufügen. Das könnte Ihre Abfrage beschleunigen, hängt jedoch von den Daten in der Tabelle ab.

Wenn Sie ein Upgrade auf SQL Server 2016 durchführen können, können Sie einen nicht gruppierten Columnstore-Index für die Tabelle erstellen. Dadurch werden COUNT(*)Abfragen extrem schnell und der Aufwand für DML-Vorgänge ist geringer. Hier ist eine kurze Demo:

DROP TABLE IF EXISTS #Items;

CREATE TABLE #Items (
    CLUST_KEY BIGINT NOT NULL,
    SMALL_COLUMN TINYINT NOT NULL,
    FILLER VARCHAR(50) NOT NULL,
    PRIMARY KEY (CLUST_KEY)
);

INSERT INTO #Items WITH (TABLOCK)
SELECT
    ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    , 1
    , REPLICATE('Z', 50)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

CREATE INDEX NCI ON #Items (SMALL_COLUMN);

SET STATISTICS TIME ON;

-- CPU time = 312 ms,  elapsed time = 320 ms.
SELECT COUNT(*)
FROM #Items
OPTION (MAXDOP 1);


CREATE NONCLUSTERED COLUMNSTORE INDEX NCCI ON #Items (SMALL_COLUMN);

-- CPU time = 0 ms,  elapsed time = 1 ms.
SELECT COUNT(*)
FROM #Items
OPTION (MAXDOP 1);

Mit der NCCI kann ich sechs Millionen Zeilen in weniger als 20 ms zählen.

Joe Obbish
quelle
1
  1. Sie können eine ID-Spalte hinzufügen, die Sie manuell aktualisieren, sodass es immer in der Reihenfolge Regen, Sommer oder Winter kommt.

  2. Wenn Sie eine einzelne Tabelle haben und es keine Where-Bedingung oder Join gibt, dann

     SELECT o.NAME
    ,o.schema_id
    ,ddps.row_count
    FROM sys.indexes AS i
    INNER JOIN sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID
    INNER JOIN sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID
      AND i.index_id = ddps.index_id
    WHERE i.index_id < 2
      AND o.is_ms_shipped = 0
      AND o.NAME = 'even'
      AND o.schema_id = 1
    

Ich höre, dass es nicht von aktualisierten Statistiken abhängt. Ich bin nicht sicher.

  1. sp_spaceused employee Dies hängt von aktualisierten Statistiken ab.

  2. Sie können so etwas wie einen Job erstellen, der einmal ausgeführt wird und die neueste ID und Anzahl speichert

    Erstellen Sie die Tabelle ItemCount als Latestid int nicht null, LatestCount int nicht null

Die Itemcount-Tabelle enthält immer nur 1 Zeilen und benötigt keinen Index

insert into ItemCount (Latestid,LatestCount)
select top 1  itemid
,(select count(*) from [dbo].[Items])LatestCount
from [dbo].[Items]
order by itemid DESC

- Hier ist die Zähllogik Ihre eigene

Wenn Ihre Anfrage eine Zählung erfordert, können Sie dies tun:

declare @LatestID INT
declare @LatestCount int

select @LatestID=LatestID,@LatestCount=LatestCount 
from ItemCount ic 

declare @FreshCount int
declare @NewCount int
SELECT @FreshCount=COUNT(1)  FROM [dbo].[Items] it
where it.itemid>=@LatestID 

set @NewCount=@FreshCount+@LatestCount

Hier [dbo].[Items]sollte die Itemid-Spalte indiziert werden

Dies ist auch geeignet, wenn join and filterIhre count (*)Anfrage eine Bedingung enthält

KumarHarsh
quelle