Ottimizzazione delle query per oltre 25 milioni di righe


11

Sto usando MS SQL e devo eseguire diverse query sulla stessa tabella con criteri diversi. All'inizio ho eseguito ogni query sulla tabella originale sebbene condividessero tutti alcuni filtri (ad es. Data, stato). Ciò ha richiesto molto tempo (circa 2 minuti).

Esistono duplicati nelle righe di dati e tutti gli indici NON SONO CLUSTER. Sono interessato solo a 4 colonne per i miei criteri e il risultato dovrebbe generare solo il conteggio, per tutte le query.

colonne necessarie: TABLE, FIELD, AFTER, DATE, e v'è un indice a ciascuno di DATEe TABLE.

Dopo aver creato una tabella temporanea con solo i campi di cui ho bisogno, è scesa a 1:40 minuti, il che è ancora molto male.

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'

Esegui questo -> (216598 righe interessate)

Poiché non tutte le query si basano sull'intervallo di date, non l'ho incluso nella query. Il problema è che l' inserimento richiede solo molto più di 1 minuto . L'inserto sopra ha richiesto 1:19 minuti

Voglio eseguire qualcosa di simile per diverse query:

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

È un problema con l'inserimento maggiore di quello della selezione, ma la temperatura ha molte meno righe della tabella originale, il che potrebbe essere meglio che passare attraverso la tabella più volte.

Come posso ottimizzarlo?

MODIFICARE

Ho rimosso l'ID ordinamento, ho pensato che il problema fosse principalmente con la selezione e non l'inserimento. Era un'ipotesi.

Non riesco a creare un unico su alcun indice poiché non esiste un campo o righe univoci.

Sto usando SQL Server 2012.

Informazioni sulla tabella : è un heap e utilizza il seguente spazio:

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

@MikaelEriksson Non riesco a modificare le tabelle di produzione ..
Atieh,

Se le query che stai tentando di ottimizzare sono nel modulo SELECT COUNT(*) AS COUNT FROM original_table WHERE AFTER = 'R' AND DATE >= '2014-01-01' AND DATE < '2015-01-01', perché non provi a ottimizzare ciascuna (query) separatamente? Non ti è permesso aggiungere indici alla tabella?
ypercubeᵀᴹ

2
Devi determinare perché è lento. Viene bloccato? Sta aspettando che tempdb cresca? Il piano di esecuzione è abissale? Nessuno può risolvere "la mia domanda è lenta" senza ulteriori dettagli ...
Aaron Bertrand

3
Bene, mi sembra una causa persa ( "Non sono autorizzato a ottimizzare nulla, quindi consente di inserire 200.000 righe in una tabella temporanea ogni volta che è necessario eseguire alcune query" ). Ma potresti rimuovere le colonne TABLEe FIELDdalla #temptabella (tutte le righe hanno TABLE = 'OTB' AND FIELD = 'STATUS'per la tabella temporanea specifica dopo tutto).
ypercubeᵀᴹ

2
Ho chiesto una modifica e miglioramenti aggiungendo un commento dettagliato (e cortese). Ecco a cosa servono i commenti. È inoltre necessario contrassegnare la domanda con la versione di SQL Server in uso (ad esempio, SQL Server 2014). Anche il DDL per la tabella potrebbe essere utile ( CREATE TABLEistruzione). Il voto negativo è dovuto al fatto che la domanda non era chiara.
Paul White 9

Risposte:


12

La domanda riguarda principalmente come ottimizzare l'istruzione select:

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

Rimozione delle proiezioni ridondanti e aggiunta dello dboschema presunto :

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

Senza un indice come ([TABLE],[FIELD]) INCLUDE ([AFTER],[DATE])SQL Server ha due opzioni principali:

  1. Scansiona interamente l'heap (3 GB +); o
  2. Individua le righe corrispondenti [TABLE] = 'OTB'e [FIELD] = 'STATUS'(usando IDX6), quindi esegui una ricerca heap (RID) per riga per recuperare le colonne [AFTER]e [DATE].

Il fatto che l'ottimizzatore scelga una scansione heap o una ricerca dell'indice con la ricerca RID dipende dalla selettività stimata dei predicati [TABLE] = 'OTB'e [FIELD] = 'STATUS'. Controlla se il numero stimato di righe dalla ricerca corrisponde alla realtà. In caso contrario, aggiorna le tue statistiche. Testare la query con un suggerimento tabella che impone l'uso dell'indice, se tale condizione è ragionevolmente selettiva . Se l'ottimizzatore sta attualmente scegliendo la ricerca dell'indice, testare le prestazioni con un INDEX(0)o un FORCESCANsuggerimento per scansionare l'heap.

Oltre a ciò, potresti cercare di migliorare leggermente la scansione dell'heap rimuovendo parte dello spazio inutilizzato (370 MB). In SQL Server 2008 questo può essere fatto ricostruendo l'heap. Lo spazio inutilizzato negli heap risulta spesso dalle eliminazioni eseguite senza un blocco della tabella (senza un blocco della tabella, le pagine vuote non vengono allocate da un heap). Per questo motivo, le tabelle che subiscono frequenti eliminazioni vengono spesso archiviate come una tabella raggruppata.

Le prestazioni della scansione dell'heap dipendono dalla quantità di tabella archiviata in memoria, da quanto deve essere letto dal disco, da quanto sono piene le pagine, dalla velocità della memoria permanente, indipendentemente dal fatto che la scansione sia I / O o CPU ( il parallelismo può aiutare).

Se le prestazioni sono ancora inaccettabili dopo aver esaminato tutto quanto sopra, provare a sostenere un nuovo indice. Se disponibile sulla versione di SQL Server, un possibile indice filtrato per la query data sarebbe:

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

Considera anche la compressione dell'indice, se disponibile e vantaggiosa. Senza un nuovo indice di qualche tipo, c'è relativamente poco che puoi fare per migliorare le prestazioni di una determinata query.


Siamo spiacenti Paul, c'è: IDX6 nonclustered located on PRIMARY TABLE, FIELD. Forse questo cambierebbe le cose che hai menzionato?
Atieh,

6

Penso che ci sia un caso per cambiare gli indici qui perché:

  • hai un compito da svolgere (queste query multiple)
  • volumi di data warehouse (oltre 25 milioni di righe) e
  • un problema di prestazioni.

Questo sarebbe anche un buon caso d'uso per gli indici columnstore non cluster introdotti in SQL Server 2012, ovvero riassumere / aggregare alcune colonne su una tabella di grandi dimensioni con molte colonne.

Sebbene questi indici abbiano l'effetto collaterale di rendere la tabella di sola lettura (ad eccezione del cambio di partizione), possono trasformare le prestazioni delle query aggregate nelle giuste condizioni. L'aspetto di sola lettura può essere gestito, eliminando e ricreando nella tabella l'indice o semplicemente i dati dell'interruttore di partizione.

Ho creato un semplice banco di prova per imitare la tua configurazione e ho visto un buon miglioramento delle prestazioni:

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

I miei risultati, 6 secondi contro 0,08 secondi:

inserisci qui la descrizione dell'immagine

In sintesi, prova a creare un caso con il tuo capo per far cambiare gli indici o almeno creare un qualche tipo di processo durante la notte in cui questi record sono scolpiti in una tabella / database di report di sola lettura dove puoi fare il tuo lavoro e aggiungi l'indicizzazione appropriato per quel carico di lavoro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.