La table partitionnée est vide et je loge des données pour 1 partition qui a des rangées de 180k. J'ai désactivé les index et chargé les données et reconstruisez les index après le chargement des données.
Tout en examinant le plan de requête des index reconstruits, je peux voir que le "nombre estimé de lignes" est 180k, mais "nombre réel de lignes" est de 300 partitions * 180 000 rangées = 54 millions de lignes, même si je charge des données pour une seule partition.
Pouvez-vous éclairer ce comportement et comment surmonter ce problème?
Microsoft SQL Server 2016 (SP2) (KB4052908) - 13.0.5026.0 (X64) Mar 18 2018 09:11:49 Copyright (c) Microsoft Corporation Enterprise Edition (64 bits) sur Windows Server 2012 R2 Standard 6.3 (Build 9600 :)
Tout en examinant le plan de requête des index reconstruits, je peux voir que le "nombre estimé de lignes" est 180k, mais "nombre réel de lignes" est de 300 partitions * 180 000 rangées = 54 millions de lignes, même si je charge des données pour une seule partition.
Vos mathématiques sont un peu désactivées ici. L'image fournie indique environ 1 853 250 lignes (non 180k) et un total de 674 582 272 rangées (pas 54 millions) sur 364 itérations (364 * 1 853 250 = 674 582,272).
Néanmoins, la question reste: pourquoi SQL Server lit-il 674 millions de lignes pour reconstruire un indice non clusterné lorsque toute la table contient uniquement 1 853 250 lignes?
Le plan d'exécution indiqué est un jointure colocated. Il effectue les opérations suivantes:
C'est évidemment une manière terriblement inefficace d'aller sur les choses. Mais l'optimiseur n'est pas folle: l'idée générale est sonore, le problème est que le filtre est appliqué trop tard. Normalement, le prédicat "Partition actuelle" serait repoussé dans l'analyse d'index en cluster, où elle apparaîtrait comme une recherche de sélection de la partition actuelle.
Le plan parallèle habituel généré ressemble à ceci:
Notez le manque d'opérateur de filtrage. Son prédicat a été poussé dans le scan:
Oui, c'est une analyse avec une recherche. L'idée est d'aller chercher des lignes de la seule partition actuelle sur chaque itération de la boucle. Chaque itération construit une partition de l'indice non clustered.
J'ai réussi à reproduire le plan d'exécution indiqué dans la question en utilisant un type de données float
ou bigint
comme colonne de partitionnement. Un certain aspect de l'estimation de la cardinalité du filtre (probablement une supposition) empêche l'optimiseur de pousser le prédicat "Partition actuelle" au-delà du tri et dans l'analyse de l'index en cluster.
La démonstration suivante crée une table de 300 partitions avec un indice non clustered désactivé, charge un million de lignes en une seule partition, puis reconstruit l'indice non clusterné. Il utilise le type de données bigint
:
DROP TABLE IF EXISTS dbo.Test;
IF EXISTS (SELECT * FROM sys.partition_schemes AS PS WHERE PS.[name] = N'PS')
DROP PARTITION SCHEME PS;
IF EXISTS (SELECT * FROM sys.partition_functions AS PF WHERE PF.[name] = N'PF')
DROP PARTITION FUNCTION PF;
GO
CREATE PARTITION FUNCTION PF (bigint)
AS RANGE RIGHT
FOR VALUES
(
0,
1000000,2000000,3000000,4000000,5000000,6000000,7000000,8000000,9000000,10000000,
11000000,12000000,13000000,14000000,15000000,16000000,17000000,18000000,19000000,
20000000,21000000,22000000,23000000,24000000,25000000,26000000,27000000,28000000,
29000000,30000000,31000000,32000000,33000000,34000000,35000000,36000000,37000000,
38000000,39000000,40000000,41000000,42000000,43000000,44000000,45000000,46000000,
47000000,48000000,49000000,50000000,51000000,52000000,53000000,54000000,55000000,
56000000,57000000,58000000,59000000,60000000,61000000,62000000,63000000,64000000,
65000000,66000000,67000000,68000000,69000000,70000000,71000000,72000000,73000000,
74000000,75000000,76000000,77000000,78000000,79000000,80000000,81000000,82000000,
83000000,84000000,85000000,86000000,87000000,88000000,89000000,90000000,91000000,
92000000,93000000,94000000,95000000,96000000,97000000,98000000,99000000,100000000,
101000000,102000000,103000000,104000000,105000000,106000000,107000000,108000000,109000000,
110000000,111000000,112000000,113000000,114000000,115000000,116000000,117000000,118000000,119000000,
120000000,121000000,122000000,123000000,124000000,125000000,126000000,127000000,128000000,129000000,
130000000,131000000,132000000,133000000,134000000,135000000,136000000,137000000,138000000,139000000,
140000000,141000000,142000000,143000000,144000000,145000000,146000000,147000000,148000000,149000000,
150000000,151000000,152000000,153000000,154000000,155000000,156000000,157000000,158000000,159000000,
160000000,161000000,162000000,163000000,164000000,165000000,166000000,167000000,168000000,169000000,
170000000,171000000,172000000,173000000,174000000,175000000,176000000,177000000,178000000,179000000,
180000000,181000000,182000000,183000000,184000000,185000000,186000000,187000000,188000000,189000000,
190000000,191000000,192000000,193000000,194000000,195000000,196000000,197000000,198000000,199000000,
200000000,201000000,202000000,203000000,204000000,205000000,206000000,207000000,208000000,209000000,
210000000,211000000,212000000,213000000,214000000,215000000,216000000,217000000,218000000,219000000,
220000000,221000000,222000000,223000000,224000000,225000000,226000000,227000000,228000000,229000000,
230000000,231000000,232000000,233000000,234000000,235000000,236000000,237000000,238000000,239000000,
240000000,241000000,242000000,243000000,244000000,245000000,246000000,247000000,248000000,249000000,
250000000,251000000,252000000,253000000,254000000,255000000,256000000,257000000,258000000,259000000,
260000000,261000000,262000000,263000000,264000000,265000000,266000000,267000000,268000000,269000000,
270000000,271000000,272000000,273000000,274000000,275000000,276000000,277000000,278000000,279000000,
280000000,281000000,282000000,283000000,284000000,285000000,286000000,287000000,288000000,289000000,
290000000,291000000,292000000,293000000,294000000,295000000,296000000,297000000,298000000,299000000,
300000000
);
CREATE PARTITION SCHEME PS
AS PARTITION PF
ALL TO ([PRIMARY]);
GO
CREATE TABLE dbo.Test
(
c1 bigint NOT NULL,
c2 bigint NOT NULL,
CONSTRAINT [PK dbo.Test (c1)]
PRIMARY KEY CLUSTERED (c1)
ON PS (c1),
INDEX [IX dbo.Test (c2)]
NONCLUSTERED (c2)
ON PS (c1)
)
ON PS (c1);
GO
ALTER INDEX [IX dbo.Test (c2)]
ON dbo.Test
DISABLE;
GO
INSERT dbo.Test WITH (TABLOCKX)
(
c1,
c2
)
SELECT
CONVERT(bigint, SV1.number * 1000 + SV2.number),
CONVERT(bigint, (SV1.number * 1000 + SV2.number) * 2)
FROM master.dbo.spt_values AS SV1
CROSS JOIN master.dbo.spt_values AS SV2
WHERE
SV1.[type] = N'P'
AND SV2.[type] = N'P'
AND SV1.number >= 0
AND SV1.number < 1000
AND SV2.number >= 0
AND SV2.number < 1000;
GO
ALTER INDEX [IX dbo.Test (c2)]
ON dbo.Test
REBUILD;
Le plan de reconstruction d'index prend autour 90 secondes Pour exécuter:
En revanche, lorsque la colonne de partitionnement est un type de données integer
, la reconstruction prend deux secondes (avec le plan de recherche à balayage efficace précédemment montré).
Cela pourrait être parce que l'ID de partition est nativement un integer
(aucune conversion n'est nécessaire dans le prédicat du filtre), mais les détails ne sont pas clairs à ce stade.
Ce qui précède ne se reproduit que pour moi si j'utilise le modèle d'estimation de cardinalité par défaut (moderne). Il n'est pas possible de spécifier un indice directement sur une déclaration de reconstruction d'index, de sorte que les utilisations suivantes utilisent le drapeau de trace documenté 9481 pour utiliser temporairement le modèle d'estimation de cardinalité hérité (original) à la place:
ALTER INDEX [IX dbo.Test (c2)]
ON dbo.Test
DISABLE;
DBCC TRACEON (9481);
ALTER INDEX [IX dbo.Test (c2)]
ON dbo.Test
REBUILD;
DBCC TRACEOFF (9481);
Cela produit le plan d'exécution optimal et se termine autour de deux secondes.
Il existe un meilleur moyen d'ajouter ou de charger des données en une seule partition d'une table. L'idée est de SWITCH
la partition existante dans une table autonome, chargez-la dans celle-ci, puis SWITCH
la partition de retour dans:
DROP TABLE IF EXISTS dbo.TestSwitchP2;
GO
-- Table with constraints limiting values to the target partition
CREATE TABLE dbo.TestSwitchP2
(
c1 float NOT NULL PRIMARY KEY,
c2 float NOT NULL,
CHECK (c1 >= 0 AND c1 < 1000000)
)
ON [PRIMARY];
GO
-- Switch existing rows into the working table
ALTER TABLE dbo.Test
SWITCH PARTITION 2
TO dbo.TestSwitchP2;
GO
-- Add new rows
INSERT dbo.TestSwitchP2 WITH (TABLOCKX)
(
c1,
c2
)
SELECT
CONVERT(float, SV1.number * 1000 + SV2.number),
CONVERT(float, (SV1.number * 1000 + SV2.number) * 2)
FROM master.dbo.spt_values AS SV1
CROSS JOIN master.dbo.spt_values AS SV2
WHERE
SV1.[type] = N'P'
AND SV2.[type] = N'P'
AND SV1.number >= 0
AND SV1.number < 1000
AND SV2.number >= 0
AND SV2.number < 1000;
GO
-- Create a compatible nonclustered index
CREATE INDEX i ON dbo.TestSwitchP2 (c2);
GO
-- Switch the partition back in to the main table
ALTER TABLE dbo.TestSwitchP2
SWITCH TO dbo.Test
PARTITION 2;
La commutation de partition est une opération de métadonnées uniquement et complète généralement instantanément.
La mise en œuvre ci-dessus charge l'un million de nouvelles lignes en moins de deux secondes. Remarque Il n'est pas nécessaire de désactiver l'index non clusterné sur la table principale.