In scenari di caricamenti massivi la best practice è sempre una, detta e ridetta:
Bene.
Ora la domanda è: in che ordine eseguo le singole operazioni?
Intendo dire:
Supponendo che la mia tabella abbia sia un indice cluster, che uno o più indici noncluster: in che ordine eseguo le azioni da fare?
Chi cancello per primo? Il cluster o il non cluster? E, una volta terminato il caricamento, chi creo per primo?
Ma ci sarà una differenza nell’eseguire un’azione prima dell’altra?
Le domande dovrebbero essere banali ma, da quanto vedo, non è così chiara la risposta.
Provo a ragionare su due possibili serie di azioni:
Prima di proseguire, provate a chiedervi quali delle due strade prendereste.
Nel ragionare, ricordiamoci semplicemente che:
Proviamo allora a rivedere con attenzione la Soluzione A che, se volessi raccontare nel dettaglio che cosa fa, diventerebbe:
E’ evidente che, con questa soluzione, obblighiamo il db engine a fare del lavoro (assolutamente inutile) e tranquillamente evitabile.
Che senso ha, quando cancello o creo gli indici, far aggiornare i noncluster?
Giusto per essere precisi: i punti 2 e 7 sono attività NON necessarie.
Possiamo correggere la cosa semplicemente dando l’ordine corretto alle nostre istruzioni, utilizzando la Soluzione B:
Facendo un test su una tabella da 2 milioni di righe, con questa struttura:
Si ottengono questi numeri (msec):
Credo parlino da sè… :-)
Guardando le statistiche di I/O ecco che cosa avviene dietro le quinte:
Soluzione A
Soluzione B
Come si può verificare anche dall’immagine, con una corretta sequenza, l’attività di DROP non fa nulla (come è giusto che sia) e l’attività di CREATE lavora solo una volta su ciascun indice.
Il consiglio? Verificate l’ordine che date a queste attività nelle vostre procedure di caricamento massivo di dati…
Di seguito lo script per ricreare il test:
USE test GO SET NOCOUNT ON; SET STATISTICS TIME OFF; DECLARE @t datetime; PRINT '--> Test NVARCHAR(MAX) INSERT'; SET @t = GETDATE( ); SELECT TOP 2000000 CAST( ROW_NUMBER( )OVER( ORDER BY C.Object_ID )AS nvarchar( 35 ))AS Col1, CAST( ROW_NUMBER( )OVER( ORDER BY C.Object_ID )AS nvarchar( 35 ))AS Col2, CAST( ROW_NUMBER( )OVER( ORDER BY C.Object_ID )AS nvarchar( 35 ))AS Col3 INTO testCluster FROM Master.sys.All_Columns C CROSS JOIN Master.sys.All_Columns C2; SELECT 'create table', DATEDIFF( MILLISECOND , @t , current_timestamp ); SET @t = GETDATE( ); ALTER TABLE testCluster ADD ColInt int NOT NULL IDENTITY( 1 , 1 ); ALTER TABLE testCluster ADD CONSTRAINT [PK] PRIMARY KEY CLUSTERED ( colInt ) SELECT 'pk', DATEDIFF( MILLISECOND , @t , current_timestamp ); /* generate index */ SET @t = GETDATE( ); create index idx1 on testCluster(col1) create index idx2 on testCluster(col2) create index idx3 on testCluster(col3) SELECT 'create index', DATEDIFF( MILLISECOND , @t , current_timestamp ); GO /* ************************** SOLUZIONE A ************************** */ /* drop index */ DECLARE @t datetime; SET @t = GETDATE( ); /* drop pk cluster, drop index */ ALTER TABLE testCluster DROP CONSTRAINT [PK] DROP INDEX idx1 ON testCluster; DROP INDEX idx2 ON testCluster; DROP INDEX idx3 ON testCluster; SELECT '--- drop index', DATEDIFF( MILLISECOND , @t , current_timestamp ); SELECT 'some stuff...'; SET @t = GETDATE( ); /* create index, create pk cluster */ CREATE INDEX idx1 ON testCluster( col1 ); CREATE INDEX idx2 ON testCluster( col2 ); CREATE INDEX idx3 ON testCluster( col3 ); ALTER TABLE testCluster ADD CONSTRAINT [PK] PRIMARY KEY CLUSTERED ( colInt ) SELECT '--- recreate index', DATEDIFF( MILLISECOND , @t , current_timestamp ); GO /* ************************** SOLUZIONE B ************************** */ /* drop index */ DECLARE @t datetime; SET @t = GETDATE( ); /* drop index, drop pk cluster */ DROP INDEX idx1 ON testCluster; DROP INDEX idx2 ON testCluster; DROP INDEX idx3 ON testCluster; ALTER TABLE testCluster DROP CONSTRAINT [PK] SELECT '--- drop index', DATEDIFF( MILLISECOND , @t , current_timestamp ); SELECT 'some stuff...'; SET @t = GETDATE( ); /* create pk cluster, create index */ ALTER TABLE testCluster ADD CONSTRAINT [PK] PRIMARY KEY CLUSTERED ( colInt ) CREATE INDEX idx1 ON testCluster( col1 ); CREATE INDEX idx2 ON testCluster( col2 ); CREATE INDEX idx3 ON testCluster( col3 ); SELECT '--- recreate index', DATEDIFF( MILLISECOND , @t , current_timestamp ); GO /* pulizia */ DROP TABLE testCluster; GO
Enjoy.
Ciao Andrea,
ottima guida, mi ha impressionato. L'ho messa subito in pratica.
La procedura su cui l'ho messa in pratica (caricamento massivo di circa 2 milioni di righe) ha immediatamente dato un buon riscontro: dai soliti 18 minuti siamo passati a soli 10. Grazie!!
Ciao Andrea! A quanto dici, aggiungo una cosa, ossia la compressione. Anche questa e' meglio rimuoverla prima del caricamento e quindi riabilitarla successivamente. Una cosa che e' poco nota, pero', e' che se si fa il drop dell'indice cluster, l'opzione di compressione rimane cmq impostata sull'HEAP ed e' quindi necessario riportare l'opzione DATA_COMPRESSION a NON manualmente.