Je me lance de configurer une vue indexée dans le scénario suivant afin que la requête suivante effectue sans deux analyses d'index en cluster. Chaque fois que je crée une vue d'index pour cette requête puis l'utiliser, il semble ignorer tout index que je lui ai mis dessus:
-- +++ THE QUERY THAT I WANT TO IMPROVE PERFORMANCE-WISE +++
SELECT TOP 1 *
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2 ON t1.PK_ID1 = t2.FK_ID1
ORDER BY t1.somethingelse1
,t2.somethingelse2;
GO
La configuration de la table est la suivante:
(Dans le script ci-dessous, il y a aussi certaines lignes pour générer des données de test, au cas où cela aide à reproduire le problème)
-- +++ TABLE SETUP +++
CREATE TABLE [dbo].[TB_test1]
(
[PK_ID1] [INT] IDENTITY(1, 1) NOT NULL
,[something1] VARCHAR(40) NOT NULL
,[somethingelse1] BIGINT NOT NULL
CONSTRAINT [PK_TB_test1] PRIMARY KEY CLUSTERED ( [PK_ID1] ASC )
);
GO
create TABLE [dbo].[TB_test2]
(
[PK_ID2] [INT] IDENTITY(1, 1) NOT NULL
,[FK_ID1] [INT] NOT NULL
,[something2] VARCHAR(40) NOT NULL
,[somethingelse2] BIGINT NOT NULL
CONSTRAINT [PK_TB_test2] PRIMARY KEY CLUSTERED ( [PK_ID2] ASC )
);
GO
ALTER TABLE [dbo].[TB_test2] WITH CHECK ADD CONSTRAINT [FK_TB_Test1] FOREIGN KEY([FK_ID1])
REFERENCES [dbo].[TB_test1] ([PK_ID1])
GO
ALTER TABLE [dbo].[TB_test2] CHECK CONSTRAINT [FK_TB_Test1]
GO
-- +++ TABLE DATA GENERATION +++
-- this might not be the quickest way, but it's only to set up test data
INSERT INTO dbo.TB_test1
( something1, somethingelse1 )
VALUES ( CONVERT(VARCHAR(40), NEWID()) -- something1 - varchar(40)
,ISNULL(ABS(CHECKSUM(NewId())) % 92233720368547758078, 1) -- somethingelse1 - bigint
)
GO 100000
RAISERROR( 'Finished setting up dbo.TB_test1', 0, 1) WITH NOWAIT
GO
INSERT INTO dbo.TB_test2
( FK_ID1, something2, somethingelse2 )
VALUES ( ISNULL(ABS(CHECKSUM(NewId())) % ((SELECT MAX(PK_ID1) FROM dbo.TB_test1) - 1), 0) + 1 -- FK_ID1 - int
,CONVERT(VARCHAR(40), NEWID()) -- something2 - varchar(40)
,ISNULL(ABS(CHECKSUM(NewId())) % 92233720368547758078, 1) -- somethingelse2 - bigint
)
GO 100000
RAISERROR( 'Finished setting up dbo.TB_test2', 0, 1) WITH NOWAIT
GO
La vue indexée doit probablement être définie comme suit et la première requête supérieure résultante est ci-dessous. Mais quels indices ai-ils besoin pour que cette requête fonctionne mieux que sans la vue indexée?
CREATE VIEW VI_test
WITH SCHEMABINDING
AS
SELECT t1.PK_ID1
,t1.something1
,t1.somethingelse1
,t2.PK_ID2
,t2.FK_ID1
,t2.something2
,t2.somethingelse2
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2 ON t1.PK_ID1 = t2.FK_ID1
GO
SELECT TOP 1 * FROM dbo.VI_test ORDER BY somethingelse1,somethingelse2
GO
Il semble ignorer tout index que je l'ai mis dessus
Sauf si vous utilisez SQL Server Enterprise Edition (ou équivalent, essai et développeur), vous devrez utiliser WITH (NOEXPAND)
sur la référence de la vue afin de l'utiliser. En fait, même si vous utilisez l'entreprise, il y a bonnes raisons d'utiliser cet indice .
Sans l'indice, l'optimiseur de requêtes (dans l'édition d'entreprise) peut effectuer un choix de coûts entre l'utilisation de la vue matérialisée ou l'accès aux tables de base. Lorsque la vue est aussi importante que les tables de base, ce calcul peut favoriser les tables de base.
Un autre point d'intérêt est que sans indice NOEXPAND
, voir les références sont toujours étendues à la requête de base avant que l'optimisation commence. À mesure que l'optimisation progresse, l'optimiseur peut ou non être en mesure de correspondre à la définition expansée à la vue matérialisée, en fonction de l'activité d'optimisation précédente. Ce n'est presque certainement pas le cas avec votre requête simple, mais je le mentionne pour la complétude.
Ainsi, à l'aide de l'indice de table NOEXPAND
est votre option principale, mais vous pouvez également penser à la fabrication des clés de la table de base et des colonnes nécessaires à la commande dans la vue. Créez un index en cluster unique sur les colonnes de touches combinées, puis un index non clusterisé séparé sur les colonnes de commande.
Cela réduira la taille de la vue matérialisée et limitera le nombre de mises à jour automatiques à faire pour maintenir la vue synchronisée avec les tables de base. Votre requête peut alors être écrite pour aller chercher les 1 touches TOP 1 dans la commande requise à partir de la vue (idéalement avec NOEXPAND
), puis rejoignez les tables de base pour récupérer les colonnes restantes à l'aide des touches de la vue.
Une autre variante consiste à regrouper la vue sur les colonnes de commande et les touches de table, puis écrivez la requête pour extraire manuellement les colonnes de non-vue de la table de base à l'aide des touches. La meilleure option pour vous dépend du contexte plus large. Un bon moyen de décider est de le tester avec les données réelles et la charge de travail.
CREATE VIEW VI_test
WITH SCHEMABINDING
AS
SELECT
t1.PK_ID1,
t1.something1,
t1.somethingelse1,
t2.PK_ID2,
t2.FK_ID1,
t2.something2,
t2.somethingelse2
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2
ON t1.PK_ID1 = t2.FK_ID1;
GO
-- Brute force unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.VI_test
(somethingelse1, somethingelse2, PK_ID1, PK_ID2);
GO
SELECT TOP (1) *
FROM dbo.VI_test WITH (NOEXPAND)
ORDER BY somethingelse1,somethingelse2;
Plan d'exécution:
-- Minimal unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.VI_test
(PK_ID1, PK_ID2)
WITH (DROP_EXISTING = ON);
GO
-- Nonclustered index for ordering
CREATE NONCLUSTERED INDEX ix
ON dbo.VI_test (somethingelse1, somethingelse2);
Plan d'exécution:
Il y a une recherche dans ce plan, mais il n'est utilisé que pour chercher une seule ligne.
ALTER VIEW VI_test
WITH SCHEMABINDING
AS
SELECT
t1.PK_ID1,
t2.PK_ID2,
t1.somethingelse1,
t2.somethingelse2
FROM dbo.TB_test1 t1
INNER JOIN dbo.TB_test2 t2
ON t1.PK_ID1 = t2.FK_ID1;
GO
-- Unique clustered index
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.VI_test
(somethingelse1, somethingelse2, PK_ID1, PK_ID2);
Mettre en doute:
SELECT TOP (1)
V.PK_ID1,
TT1.something1,
V.somethingelse1,
V.PK_ID2,
TT2.FK_ID1,
TT2.something2,
V.somethingelse2
FROM dbo.VI_test AS V WITH (NOEXPAND)
JOIN dbo.TB_test1 AS TT1 ON TT1.PK_ID1 = V.PK_ID1
JOIN dbo.TB_test2 AS TT2 ON TT2.PK_ID2 = V.PK_ID2
ORDER BY somethingelse1,somethingelse2;
Plan d'exécution:
Cela indique que les touches de table ont été récupérées (une seule ligne d'extraction de la ligne à partir de l'indice en cluster à la vue dans l'ordre suivi de deux recherches à une seule rangée sur les tables de base pour récupérer les colonnes restantes.