Nous utilisons une application fournisseur s'exécutant sur SQL Server Enterprise, et elle a une bizarrerie plutôt ennuyeuse d'exécuter des instructions COUNT
sur la table Items lors du traitement de la plupart des documents financiers (commandes, factures, etc.).
Par exemple. SELECT COUNT('A') FROM [dbo].[Items] T0
Je suis sûr que ce serait normalement bien, mais il y a plus de 6 millions d'enregistrements, et il faut environ 400 ms pour les compter tous. Cela peut constituer une partie substantielle du temps de traitement global.
La table a déjà un index NonClustered extrêmement étroit (tinyint, plus Clustered Key) qui est ce que SQL utilise quand il fait le scan de table, donc je ne pense pas que nous puissions faire mieux à cet égard.
Il y a quelques solutions que je connais, que nous aimerions éviter si possible:
COUNT_BIG(*)
Avons-nous d'autres options pour accélérer cela?
Voici un Gist montrant la configuration: https://Gist.github.com/elvishfiend/5094f120b14f8ecfb325623edcb5f3eb
La vue indexée devrait être parmi les options les plus rapides, avec la surcharge de maintenance la plus faible, lorsqu'elle est mise en œuvre de manière optimale .
Les modifications sont incrémentielles (deltas) comme je l'explique en détail dans Maintenance de la vue indexée dans les plans d'exécution (un recomptage complet n'est pas effectué à chaque mise à jour de la table de base); cependant, vous devez vous assurer que les parties de mise à jour delta du plan d'exécution disposent de méthodes d'accès efficaces (comme toute requête).
Il est généralement assez simple d'identifier un index manquant dans le plan d'exécution INSERT/UPDATE/DELETE
. Vous pourriez peut-être ajouter un plan d'exécution illustratif (réel) à votre question.
La correspondance automatique du texte de la requête à une vue indexée n'est disponible que dans Enterprise Edition (et équivalents). Dans les autres éditions, vous devez utiliser l'indicateur de table WITH (NOEXPAND)
. Il existe également bonnes raisons pour utiliser NOEXPAND
même sur Enterprise Edition.
Concernant le code de démonstration: assurez-vous de spécifier l'indice à l'aide de WITH (NOEXPAND)
. De la façon dont vous l'avez écrit, NOEXPAND
est analysé comme un alias. Notez également que seules les vues matérialisées (indexées) peuvent avoir un indice NOEXPAND
.
Si vous ne parvenez pas à ajouter un indice directement, ce serait une excellente utilisation d'un guide de plan. Un guide de plan peut également être utilisé pour garantir qu'une requête qui correspond à une vue indexée (sans la nommer explicitement) utilise réellement la vue indexée.
N'oubliez pas que sans NOEXPAND
sur une vue matérialisée (indexée), SQL Server étend toujours la définition de la vue au début de la compilation du plan. Enterprise Edition peut (ou peut ne pas) faire correspondre (parties de) une requête à une vue indexée en fonction de son évaluation des coûts de chaque option.
Questions et réponses connexes:
Si vous êtes bloqué sur SQL Server 2012, vous pouvez essayer de créer un index uniquement sur la clé d'index cluster. Il peut être un peu plus petit qu'un index sur une colonne TINYINT
. Vous pouvez également essayer d'ajouter la compression de page à votre index. Cela pourrait rendre votre requête plus rapide, mais cela dépend des données du tableau.
Si vous pouvez mettre à niveau vers SQL Server 2016, vous pouvez créer un index columnstore non cluster sur la table. Cela rendra les requêtes COUNT(*)
extrêmement rapides avec un surcoût inférieur sur les opérations DML. Voici une démo rapide:
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);
Avec le NCCI, je peux compter six millions de lignes en moins de 20 ms.
vous pouvez ajouter une colonne id que vous mettez à jour manuellement de telle sorte que soit toujours en séquence la pluie, l'été ou l'hiver.
Si vous avez une table unique et qu'il n'y a pas de condition ou de jointure, alors
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
J'entends que cela ne dépend pas de statistiques mises à jour. Je ne suis pas sûr.
sp_spaceused employee
Cela dépend des statistiques mises à jour.
vous pouvez créer quelque chose comme un travail qui s'exécute une fois et stocker le dernier identifiant et compter
créer la table ItemCount comme Latestid int non null, LatestCount int non null
La table Itemcount contient toujours seulement 1 lignes et aucun index n'est nécessaire
insert into ItemCount (Latestid,LatestCount)
select top 1 itemid
,(select count(*) from [dbo].[Items])LatestCount
from [dbo].[Items]
order by itemid DESC
- Ici, la logique de comptage est la vôtre
donc chaque fois que votre requête nécessite un décompte, vous pouvez le faire,
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
ici la colonne itemid de [dbo].[Items]
doit être indexée
Cela conviendra également si vous avez la condition join and filter
Dans votre requête count (*)