web-dev-qa-db-fra.com

Colonne d'identité dans l'index de colonne

J'ai une table extrêmement grande imo (~ 137 millions de lignes) avec beaucoup de données répétées, de nombreuses colonnes NULL, etc.

Je envisage d'explorer cela en utilisant une table avec un COLUMNSTORE INDEX et j'ai une colonne IDENTITY dans la table d'origine, qui est ma seule colonne où chaque ligne est unique.

Devrais-je laisser cette colonne ou l'inclure? J'ai lu que vous voulez inclure toutes les lignes de votre table dans le COLUMNSTORE INDEX Mais j'ai également lu que les meilleurs candidats sont des colonnes avec beaucoup de lignes non uniques.

Est-ce juste un mauvais candidat pour un COLUMNSTORE INDEX?

J'utilise SQL Server 2012, il s'agit donc d'une colonne non planée. Je viens d'explorer de meilleurs moyens de stocker ces données. Les mises à jour sont inexistantes, bien que de nouvelles lignes soient annoncées périodiquement via un processus ELT, donc je suppose que certains travaux seraient effectués là-bas. Certaines personnes miennent ces données et génèrent d'énormes rapports, beaucoup de numérisation des lignes, apporte le serveur à une analyse à des moments qui nous ont forcé à décharger une copie quotidienne sur un serveur secondaire.

9
Don

Les colonnes d'identité ne sont pas véritablement compressées dans les index de ColumnStore dans SQL Server 2012 ou dans SQL Server 2014. Il dépendra tous vraiment de la charge de travail que vous rencontrez. Si votre charge de travail inclura la colonne Identity, vous pouvez très belle tirer parti de élimination du segment.

Du point de vue de la compression - Columnstore vous fournira une meilleure compression que la page. Typiquement. S'il vous plaît, testez-le avant de faire avancer la production.

Votre plus gros problème dans SQL Server 2012 sera une implémentation très faible du mode de lot et vous ne pouvez rien faire à ce sujet.

11
Niko Neugebuer

Je n'ai pas pu résister à rejoindre Niko avec une autre réponse (bienvenue, Niko!). En général, je suis d'accord avec Niko que les limitations de mode de lot dans SQL 2012 (si NIKO ne liera pas à son propre blog, je vais :)) peut être une préoccupation majeure. Mais si vous pouvez vivre avec ceux-ci et avoir le contrôle total sur chaque requête, vous écrivez contre la table pour y participer soigneusement, Columnstore pourrait fonctionner pour vous dans SQL 2012.

En ce qui concerne vos questions spécifiques sur la colonne Identity, j'ai constaté que la colonne d'identité se compresse très bien et recommanderait vivement l'inclure dans votre index de colonne dans tous les tests initiaux. (Notez que si la colonne Identity est également l'indice en cluster de votre arbre B, il sera automatiquement inclus dans votre index de colonne non clustered .)

Pour référence, voici les tailles que j'ai observées pendant environ 10 mm de données de colonne d'identité. La colonne de colonne chargée pour élimination optimale du segment comprime à 26 Mo (contre 113 Mo pour PAGE compression de la table de rangée), et même la colonne construite sur un arbre B commandé aléatoirement est de 40 Mo . Cela montre donc une énorme prestation de compression, même sur la meilleure compression B-Tree SQL a à offrir et même si vous ne vous inquiétez pas d'aligner vos données pour une élimination optimale du segment (que vous feriez en créant d'abord un arbre B, puis Construire votre colonne de colonne avec MAXDOP 1).

enter image description here

Voici le script complet que j'ai utilisé au cas où vous aimeriez jouer:

-- Confirm SQL version
SELECT @@version
--Microsoft SQL Server 2012 - 11.0.5613.0 (X64) 
--  May  4 2015 19:05:02 
--  Copyright (c) Microsoft Corporation
--  Enterprise Edition: Core-based Licensing (64-bit) on Windows NT 6.3 <X64> (Build 9600: )


-- Create a columnstore table with identity column that is the primary key
-- This will yield 10 columnstore segments @ 1048576 rows each
SELECT i = IDENTITY(int, 1, 1), ROW_NUMBER() OVER (ORDER BY randGuid) as randCol
INTO #testIdentityCompression_sortedColumnstore
FROM (
    SELECT TOP 10485760 ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS randI, NEWID() AS randGuid
    FROM master..spt_values v1
    CROSS JOIN master..spt_values v2
    CROSS JOIN master..spt_values v3
) r
ORDER BY r.randI
GO
ALTER TABLE #testIdentityCompression_sortedColumnstore
ADD PRIMARY KEY (i)
GO
-- Load using a pre-ordered b-tree and one thread for optimal segment elimination
-- See http://www.nikoport.com/2014/04/16/clustered-columnstore-indexes-part-29-data-loading-for-better-segment-elimination/
CREATE NONCLUSTERED COLUMNSTORE INDEX cs_#testIdentityCompression_sortedColumnstore ON #testIdentityCompression_sortedColumnstore (i) WITH (MAXDOP = 1)
GO

-- Create another table with the same data, but randomly ordered
SELECT *
INTO #testIdentityCompression_randomOrderColumnstore
FROM #testIdentityCompression_sortedColumnstore
GO
ALTER TABLE #testIdentityCompression_randomOrderColumnstore
ADD UNIQUE CLUSTERED (randCol)
GO
CREATE NONCLUSTERED COLUMNSTORE INDEX cs_#testIdentityCompression_randomOrderColumnstore ON #testIdentityCompression_randomOrderColumnstore (i) WITH (MAXDOP = 1)
GO

-- Create a b-tree with the identity column data and no compression
-- Note that we copy over only the identity column since we'll be looking at the total size of the b-tree index
-- If anything, this gives an unfair "advantage" to the rowstore-page-compressed version since more
-- rows fit on a page and page compression rates should be better without the "randCol" column.
SELECT i
INTO #testIdentityCompression_uncompressedRowstore
FROM #testIdentityCompression_sortedColumnstore
GO
ALTER TABLE #testIdentityCompression_uncompressedRowstore
ADD PRIMARY KEY (i)
GO

-- Create a b-tree with the identity column and page compression
SELECT i
INTO #testIdentityCompression_compressedRowstore
FROM #testIdentityCompression_sortedColumnstore
GO
ALTER TABLE #testIdentityCompression_compressedRowstore
ADD PRIMARY KEY (i)
WITH (DATA_COMPRESSION = PAGE)
GO

-- Compare all the sizes!
SELECT OBJECT_NAME(p.object_id, 2) AS tableName, COUNT(*) AS num_segments, SUM(on_disk_size / (1024.*1024.)) as size_mb
FROM tempdb.sys.partitions p
JOIN tempdb.sys.column_store_segments s
    ON s.partition_id = p.partition_id
    AND s.column_id = 1
WHERE p.object_id IN (OBJECT_ID('tempdb..#testIdentityCompression_sortedColumnstore'),OBJECT_ID('tempdb..#testIdentityCompression_randomOrderColumnstore'))
GROUP BY p.object_id
UNION ALL
SELECT OBJECT_NAME(p.object_id, 2) AS tableName
    , NULL AS num_segments
    , (a.total_pages*8.0) / (1024.0) as size_mb
FROM tempdb.sys.partitions p
JOIN tempdb.sys.allocation_units a
    ON a.container_id = p.partition_id
WHERE p.object_id IN (OBJECT_ID('tempdb..#testIdentityCompression_compressedRowstore'),OBJECT_ID('tempdb..#testIdentityCompression_uncompressedRowstore'))
ORDER BY 3 ASC
GO
3
Geoff Patterson