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'ils partagent tous 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
, DATE
, et il y a un index sur chacun des DATE
et 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 que cela prend bien au-dessus de 1 minute pour insérer uniquement. L'insert 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?
MODIFIER
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.
Table Info: C'est un tas et a l'utilisation de l'espace suivante:
name rows reserved data index_size unused
mytbl 24869658 9204568 KB 3017952 KB 5816232 KB 370384 KB
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 schéma dbo
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 a deux options principales:
[TABLE] = 'OTB'
Et [FIELD] = 'STATUS'
(En utilisant IDX6
), Puis effectuez une recherche de tas (RID) par ligne pour récupérer le [AFTER]
et [DATE]
colonnes.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 une indication INDEX(0)
ou FORCESCAN
pour 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 tas résulte souvent de suppressions effectuées sans qu'un verrou de table ne soit pris (sans verrou de table, les pages vides ne sont pas désallouées d'un tas). 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 de segment de mémoire dépendent de la quantité de table stockée en mémoire, de la quantité à lire sur le disque, de la saturation 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 quelconque, vous ne pouvez pas faire grand-chose pour améliorer les performances de la requête donnée.
Je pense qu'il y a lieu de changer les index ici parce que:
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 agrégées dans les bonnes conditions. L'aspect en lecture seule peut être géré, soit en supprimant 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:
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.