Optimisation des requêtes pour plus de 25 millions de lignes


11

J'utilise MS SQL et je dois exécuter plusieurs requêtes sur la même table selon différents critères. Au début, j'ai exécuté chaque requête sur la table d'origine, bien qu'elles partagent toutes un certain filtrage (c'est-à-dire la date, le statut). Cela a pris beaucoup de temps (environ 2 minutes).

Il y a des doublons dans les lignes de données et tous les index sont NON CLUSTERES. Je ne suis intéressé que par 4 colonnes pour mes critères et le résultat devrait afficher le nombre uniquement, pour toutes les requêtes.

colonnes nécessaires: TABLE, FIELD, AFTER, DATEet il y a un index sur chacun DATEet TABLE.

Après avoir créé une table temporaire avec uniquement les champs dont j'ai besoin, elle est descendue à 1:40 minutes, ce qui est toujours très mauvais.

CREATE TABLE #TEMP
(
    TABLE VARCHAR(30) NULL,
    FIELD VARCHAR(30) NULL,
    AFTER VARCHAR(1000) NULL,
    DATE DATETIME,
    SORT_ID INT IDENTITY(1,1)
)
CREATE CLUSTERED INDEX IX_ADT ON #TEMP(SORT_ID)

INSERT INTO #TEMP (TABLE, FIELD, AFTER, DATE)
    SELECT TABLE, FIELD, AFTER, DATE 
    FROM mytbl WITH (NOLOCK)
    WHERE TABLE = 'OTB' AND
    FIELD = 'STATUS'

Runnig this -> (216598 ligne (s) affectée)

Étant donné que toutes les requêtes ne dépendent pas de la plage de dates, je ne l'ai pas incluse dans la requête. Le problème est qu'il faut plus de 1 minute pour insérer uniquement . L'insertion ci-dessus a pris 1:19 minutes

Je veux exécuter quelque chose comme ça pour plusieurs requêtes:

SELECT COUNT(*) AS COUNT
FROM #TEMP
WHERE AFTER = 'R' AND
DATE >= '2014-01-01' AND
DATE <= '2015-01-01'

C'est un problème avec l'insert plus que celui de la sélection, mais le temp a beaucoup moins de lignes que le tableau d'origine, ce qui pourrait être mieux que de parcourir le tableau plusieurs fois.

Comment puis-je optimiser cela?

ÉDITER

J'ai supprimé l'ID de tri, je pensais que le problème venait principalement de la sélection et non de l'insertion. C'était une supposition.

Je ne peux pas créer un unique sur n'importe quel index car il n'y a pas de champ ou de lignes uniques.

J'utilise SQL Server 2012.

Informations sur la table : il s'agit d'un segment de mémoire dont l'utilisation de l'espace est la suivante:

name    rows        reserved    data        index_size  unused
mytbl   24869658    9204568 KB  3017952 KB  5816232 KB  370384 KB

@MikaelEriksson Je ne peux pas modifier les tables de production ..
Atieh

Si les requêtes que vous essayez d'optimiser sont de la forme SELECT COUNT(*) AS COUNT FROM original_table WHERE AFTER = 'R' AND DATE >= '2014-01-01' AND DATE < '2015-01-01', pourquoi n'essayez-vous pas d'optimiser chacune (requête) séparément? N'êtes-vous pas autorisé à ajouter des index à la table?
ypercubeᵀᴹ

2
Vous devez déterminer pourquoi c'est lent. Est-il bloqué? Attend-il que tempdb se développe? Le plan d'exécution est-il abyssal? Personne ne peut corriger "ma requête est lente" sans plus de détails ...
Aaron Bertrand

3
Eh bien, cela me semble être une cause perdue ( "Je ne suis pas autorisé à optimiser quoi que ce soit, alors il suffit de pousser 200K lignes dans une table temporaire à chaque fois que nous devons exécuter des requêtes" ). Mais vous pouvez supprimer les colonnes TABLEet FIELDde la #temptable (toutes les lignes ont TABLE = 'OTB' AND FIELD = 'STATUS'pour la table temporaire spécifique après tout.)
ypercubeᵀᴹ

2
J'ai demandé une modification et des améliorations en ajoutant un commentaire détaillé (et poli). C'est à cela que servent les commentaires. Vous devez également baliser votre question avec la version de SQL Server que vous utilisez (par exemple SQL Server 2014). DDL pour la table peut également être utile ( CREATE TABLEinstruction). Le vote négatif était dû au fait que la question n'était pas claire.
Paul White 9

Réponses:


12

La question est principalement de savoir comment optimiser l'instruction select:

SELECT [TABLE], [FIELD], [AFTER], [DATE]
FROM mytbl WITH (NOLOCK)
WHERE [TABLE] = 'OTB' AND
[FIELD] = 'STATUS'

Suppression des projections redondantes et ajout du dboschéma présumé :

SELECT [AFTER], [DATE] 
FROM dbo.mytbl WITH (NOLOCK)
WHERE [TABLE] = 'OTB'
AND FIELD = 'STATUS';

Sans index comme ([TABLE],[FIELD]) INCLUDE ([AFTER],[DATE])SQL Server, deux options principales sont possibles:

  1. Scannez entièrement le tas (3 Go +); ou
  2. Recherchez les lignes correspondantes [TABLE] = 'OTB'et [FIELD] = 'STATUS'(à l'aide de IDX6), puis effectuez une recherche de tas (RID) par ligne pour récupérer les colonnes [AFTER]et [DATE].

Que l'optimiseur choisisse une analyse de tas ou une recherche d'index avec la recherche RID dépend de la sélectivité estimée des prédicats [TABLE] = 'OTB'et [FIELD] = 'STATUS'. Vérifiez si le nombre estimé de lignes de la recherche correspond à la réalité. Sinon, mettez à jour vos statistiques. Testez la requête avec un indice de table forçant l'utilisation de l'index, si cette condition est raisonnablement sélective . Si l'optimiseur choisit actuellement la recherche d'index, testez les performances avec un indice INDEX(0)ou FORCESCANpour analyser le tas.

Au-delà de cela, vous pouvez chercher à améliorer un peu l'analyse du tas en supprimant une partie de l'espace inutilisé (370 Mo). Dans SQL Server 2008, cela peut être fait en reconstruisant le tas. L'espace inutilisé dans les segments résulte souvent de suppressions effectuées sans verrouillage de table (sans verrouillage de table, les pages vides ne sont pas désallouées d'un segment). Les tables qui subissent des suppressions fréquentes sont souvent mieux stockées en tant que table en cluster pour cette raison.

Les performances de l'analyse du segment de mémoire dépendent de la quantité de table stockée en mémoire, de la quantité qui doit être lue sur le disque, du niveau de remplissage des pages, de la vitesse du stockage persistant, que l'analyse soit liée aux E / S ou au processeur ( le parallélisme peut aider).

Si les performances sont toujours inacceptables après avoir étudié tout ce qui précède, essayez de plaider en faveur d'un nouvel index. S'il est disponible sur votre version de SQL Server, un index filtré possible pour la requête donnée serait:

CREATE INDEX index_name
ON dbo.mytbl ([DATE],[AFTER])
WHERE [TABLE] = 'OTB'
AND [FIELD] = 'STATUS';

Envisagez également la compression d'index, si elle est disponible et avantageuse. Sans un nouvel index, vous ne pouvez pas faire grand-chose pour améliorer les performances de la requête donnée.


Désolé Paul, il y a: IDX6 nonclustered located on PRIMARY TABLE, FIELD. Peut-être que cela changerait les choses que vous avez mentionnées?
Atieh

6

Je pense qu'il y a lieu de changer les index ici parce que:

  • vous avez une tâche à faire (ces multiples requêtes)
  • volumes d'entrepôt de données (plus de 25 millions de lignes) et
  • un problème de performances.

Ce serait également un bon cas d'utilisation pour les index columnstore non-cluster introduits dans SQL Server 2012, c'est-à-dire résumer / agréger quelques colonnes sur une grande table avec de nombreuses colonnes.

Bien que ces index aient pour effet secondaire de rendre la table en lecture seule (à l'exception du changement de partition), ils peuvent transformer les performances des requêtes d'agrégation dans les bonnes conditions. L'aspect en lecture seule peut être géré, soit en déposant et en recréant les données d'index ou de commutateur de partition simple dans la table.

J'ai mis en place un banc d'essai simple pour imiter votre configuration et j'ai constaté une bonne amélioration des performances:

USE tempdb
GO

SET NOCOUNT ON
GO

-- Create a large table
IF OBJECT_ID('dbo.largeTable') IS NOT NULL
DROP TABLE dbo.largeTable
GO
CREATE TABLE dbo.largeTable ( 

    [TABLE] VARCHAR(30) NULL,
    FIELD VARCHAR(30) NULL,
    [AFTER] VARCHAR(1000) NULL,
    [DATE] DATETIME,
    SORT_ID INT IDENTITY(1,1),

    pad VARCHAR(100) DEFAULT REPLICATE( '$', 100 )
)
GO

-- Populate table
;WITH cte AS (
SELECT TOP 100000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT INTO dbo.largeTable ( [TABLE], FIELD, [AFTER], [DATE] )
SELECT 
    x.tableName, 
    y.field,
    z.[after],
    DATEADD( day, rn % 1111, '1 Jan 2012' )
FROM cte c
    CROSS JOIN ( VALUES ( 'OTB' ), ( 'AAA' ), ( 'BBB' ), ( 'CCCC' ) ) x ( tableName )
    CROSS JOIN ( VALUES ( 'STATUS' ), ( 'TIME' ), ( 'POWER' ) ) y ( field )
    CROSS JOIN ( VALUES ( 'R' ), ( 'X' ), ( 'Z' ), ( 'A' ) ) z ( [after] )

CHECKPOINT

GO 5

EXEC sp_spaceused 'dbo.largeTable'
GO

SELECT MIN([DATE]) xmin, MAX([DATE]) xmax, FORMAT( COUNT(*), '#,#' ) records
FROM dbo.largeTable
GO

-- Optionally clear cache for more comparable results; DO NOT RUN ON PRODUCTION SYSTEM!!
--DBCC DROPCLEANBUFFERS
--DBCC FREEPROCCACHE
--GO

DECLARE @startDate DATETIME2 = SYSUTCDATETIME()

SELECT COUNT(*) AS COUNT
FROM dbo.largeTable
WHERE [AFTER] = 'R' 
  AND [DATE] >= '2014-01-01' 
  AND [DATE] <= '2015-01-01'

SELECT DATEDIFF( millisecond, @startDate, SYSUTCDATETIME() ) diff1
GO

-- Add the non-clustered columnstore
CREATE NONCLUSTERED COLUMNSTORE INDEX _cs ON dbo.largeTable ( [TABLE], FIELD, [AFTER], [DATE] )
GO

-- Optionally clear cache for more comparable results; DO NOT RUN ON PRODUCTION SYSTEM!!
--DBCC DROPCLEANBUFFERS
--DBCC FREEPROCCACHE
--GO

-- Check query again
DECLARE @startDate DATETIME2 = SYSUTCDATETIME()

SELECT COUNT(*) AS COUNT
FROM dbo.largeTable
WHERE [AFTER] = 'R' 
  AND [DATE] >= '2014-01-01' 
  AND [DATE] <= '2015-01-01'

SELECT DATEDIFF( millisecond, @startDate, SYSUTCDATETIME() ) diff2
GO

Mes résultats, 6 secondes contre 0,08 secondes:

entrez la description de l'image ici

En résumé, essayez de créer un dossier avec votre patron pour faire changer les index ou au moins créer une sorte de processus du jour au lendemain où ces enregistrements sont découpés dans une table / base de données de rapports en lecture seule où vous pouvez faire votre travail et ajouter l'indexation approprié à cette charge de travail.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.