In qualità di DBA di SQL Server, ci occupiamo sempre di una delle cose più importanti per l'azienda, i dati. In alcuni casi, le applicazioni possono diventare piuttosto complesse e ti ritroverai con un sacco di tabelle di database sparse per le tue istanze di SQL Server. Ciò potrebbe causare alcuni inconvenienti, come ad esempio:
- Sapere come si comportano i tuoi dati ogni giorno, in termini di trend di crescita (spazio e/o quantità di righe).
- Sapere quali tabelle di database richiedono (o richiederanno) una strategia particolare/diversa per archiviare i dati perché crescono troppo velocemente.
- Sapere quale delle tue tabelle del database occupa troppo spazio, può portare a vincoli di archiviazione.
A causa dell'importanza di questi dettagli, ho creato un paio di stored procedure che possono essere di grande aiuto per qualsiasi DBA di SQL Server che desideri tenere traccia delle informazioni relative alle tabelle del database nel proprio ambiente. Credimi, uno di loro è molto bello.
Considerazioni iniziali
- Assicurati che l'account che esegue questa stored procedure disponga di privilegi sufficienti. Probabilmente potresti iniziare con sysadmin e poi andare il più granulare possibile per assicurarti che l'utente disponga del minimo dei privilegi richiesti per il corretto funzionamento dell'SP.
- Gli oggetti del database (tabella del database e stored procedure) verranno creati all'interno del database selezionato al momento dell'esecuzione dello script, quindi scegliere con attenzione.
- Lo script è realizzato in modo da poter essere eseguito più volte senza ricevere un errore. Per la stored procedure, ho utilizzato l'istruzione "CREATE OR ALTER PROCEDURE", disponibile da SQL Server 2016 SP1. Ecco perché non sorprenderti se non funziona correttamente in una versione precedente.
- Sentiti libero di cambiare i nomi degli oggetti di database creati.
- Prestare attenzione ai parametri della Stored Procedure che raccoglie i dati grezzi. Possono essere cruciali in una potente strategia di raccolta dati per visualizzare le tendenze.
Come utilizzare le stored procedure?
- Copia e incolla il codice T-SQL (disponibile in questo articolo).
- Il primo SP prevede 2 parametri:
- @persistData:'Y' se un DBA vuole salvare l'output in una tabella di destinazione e 'N' se il DBA vuole vedere l'output direttamente.
- @truncateTable:'Y' per troncare la tabella prima di memorizzare i dati acquisiti e 'N' se i dati correnti vengono mantenuti nella tabella. Tieni presente che il valore di questo parametro è irrilevante se il valore del parametro @persistData è 'N'.
- Il secondo SP prevede 1 parametro:
- @targetParameter:il nome della colonna da utilizzare per trasporre le informazioni raccolte.
Campi presentati e loro significato
- nome_database: il nome del database in cui risiede la tabella.
- schema: il nome dello schema in cui risiede la tabella.
- nome_tabella: il segnaposto per il nome della tabella.
- conteggio_righe: il numero di righe attualmente presenti nella tabella.
- spazio_totale_mb: il numero di MegaByte allocati per la tabella.
- spazio_usato_mb: il numero di MegaByte effettivamente utilizzati dalla tabella.
- spazio_inutilizzato_mb: il numero di MegaByte che la tabella non sta utilizzando.
- data_di creazione: la data/ora di creazione della tabella.
- data_collection_timestamp: visibile solo se 'Y' viene passato al parametro @persistData. Viene utilizzato per sapere quando l'SP è stato eseguito e le informazioni sono state salvate correttamente nella tabella DBA_Tables.
Test di esecuzione
Dimostrerò alcune esecuzioni delle stored procedure:
/* Visualizza le informazioni sulle tabelle per tutti i database utente */
EXEC GetTablesData @persistData = 'N',@truncateTable = 'N'
/* Mantieni le informazioni delle tabelle del database e interroga la tabella di destinazione, troncando prima la tabella di destinazione */
EXEC GetTablesData @persistData = 'Y',@truncateTable = 'Y'
SELECT * FROM DBA_Tables
Query laterali
*Query per visualizzare le tabelle del database ordinate dal maggior numero di righe al più basso.
SELECT * FROM DBA_Tables ORDER BY row_count DESC;
*Query per visualizzare le tabelle del database ordinate dallo spazio totale più grande al più basso.
SELECT * FROM DBA_Tables ORDER BY total_space_mb DESC;
*Query per visualizzare le tabelle del database ordinate dallo spazio utilizzato più grande al più basso.
SELECT * FROM DBA_Tables ORDER BY used_space_mb DESC;
*Query per visualizzare le tabelle del database ordinate dal più grande spazio inutilizzato al più basso.
SELECT * FROM DBA_Tables ORDER BY unused_space_mb DESC;
*Query per visualizzare le tabelle del database ordinate per data di creazione, dalla più recente alla meno recente.
SELECT * FROM DBA_Tables ORDER BY created_date DESC;
Ecco un codice completo della Stored Procedure che acquisisce le informazioni delle tabelle del database:
*All'inizio dello script, vedrai il valore predefinito che la stored procedure assume se non viene passato alcun valore per ciascun parametro.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE OR ALTER PROCEDURE [dbo].[GetTablesData]
@persistData CHAR(1) = 'Y',
@truncateTable CHAR(1) = 'Y'
AS
BEGIN
SET NOCOUNT ON
DECLARE @command NVARCHAR(MAX)
DECLARE @Tmp_TablesInformation TABLE(
[database] [VARCHAR](255) NOT NULL,
[schema] [VARCHAR](64) NOT NULL,
[table] [VARCHAR](255) NOT NULL,
[row_count] [BIGINT]NOT NULL,
[total_space_mb] [DECIMAL](15,2) NOT NULL,
[used_space_mb] [DECIMAL](15,2) NOT NULL,
[unused_space_mb] [DECIMAL](15,2) NOT NULL,
[created_date] [DATETIME] NOT NULL
)
SELECT @command = '
USE [?]
IF DB_ID(''?'') > 4
BEGIN
SELECT
''?'',
s.Name AS [schema],
t.NAME AS [table],
p.rows AS row_count,
CAST(ROUND(((SUM(a.total_pages) * 8) / 1024.00), 2) AS DECIMAL(15, 2)) AS total_space_mb,
CAST(ROUND(((SUM(a.used_pages) * 8) / 1024.00), 2) AS DECIMAL(15, 2)) AS used_space_mb,
CAST(ROUND(((SUM(a.total_pages) - SUM(a.used_pages)) * 8) / 1024.00, 2) AS DECIMAL(15, 2)) AS unused_space_mb,
t.create_date as created_date
FROM sys.tables t
INNER JOIN sys.indexes i ON t.OBJECT_ID = i.object_id
INNER JOIN sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
LEFT OUTER JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE t.NAME NOT LIKE ''dt%''
AND t.is_ms_shipped = 0
AND i.OBJECT_ID > 255
GROUP BY t.Name, s.Name, p.Rows,t.create_date
ORDER BY total_space_mb DESC, t.Name
END'
INSERT INTO @Tmp_TablesInformation
EXEC sp_MSForEachDB @command
IF @persistData = 'N'
SELECT * FROM @Tmp_TablesInformation
ELSE
BEGIN
IF(@truncateTable = 'Y')
TRUNCATE TABLE DBA_Tables
INSERT INTO DBA_Tables
SELECT *,GETDATE() FROM @Tmp_TablesInformation ORDER BY [database],[schema],[table]
END
END
GO
Fino a questo punto, le informazioni sembrano un po' aride, ma permettetemi di cambiare questa percezione con la presentazione di una Stored Procedure complementare. Il suo scopo principale è di trasporre le informazioni raccolte nella tabella di destinazione che funge da fonte per i rapporti sulle tendenze.
Ecco come eseguire la stored procedure:
*A scopo dimostrativo, ho inserito record manuali nella tabella di destinazione denominata t1 per simulare la mia normale esecuzione di stored procedure.
*Il set di risultati è un po' ampio, quindi prenderò un paio di screenshot per mostrare l'output completo.
EXEC TransposeTablesInformation @targetParmeter = 'row_count'
Principali da asporto
- Se automatizzi l'esecuzione dello script che popola la tabella di destinazione, puoi immediatamente notare se qualcosa è andato storto con esso o con i tuoi dati. Dai un'occhiata ai dati per la tabella "t1" e la colonna "15". Puoi vedere NULL lì che è stato fatto apposta per mostrarti qualcosa che potrebbe accadere.
- Con questo tipo di visualizzazione, puoi vedere un comportamento peculiare per le tabelle di database più importanti/critiche.
- Nell'esempio fornito, ho scelto il campo 'row_count' della tabella di destinazione, ma puoi scegliere qualsiasi altro campo numerico come parametro e ottenere lo stesso formato di tabella, ma con dati diversi.
- Non preoccuparti, se specifichi un parametro non valido, la Stored Procedure ti avviserà e ne interromperà l'esecuzione.
Ecco un codice completo della Stored Procedure che traspone le informazioni della tabella di destinazione:
*All'inizio dello script, vedrai il valore predefinito che la stored procedure assume se non viene passato alcun valore per ciascun parametro.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE OR ALTER PROCEDURE [dbo].[TransposeTablesInformation]
@targetParameter NVARCHAR(15) = 'row_count'
AS
BEGIN
SET NOCOUNT ON;
IF (@targetParameter <> 'row_count' AND @targetParameter <> 'total_space_mb' AND @targetParameter <> 'used_space_mb' AND @targetParameter <> 'unused_space_mb')
BEGIN
PRINT 'Please specify a valid parameter!'
PRINT 'i.e. row_count | total_space_mb | used_space_mb | unused_space_mb'
RETURN
END
ELSE
BEGIN
CREATE TABLE #TablesInformation(
[database] [VARCHAR](255) NOT NULL,
[schema] [VARCHAR](64) NOT NULL,
[table] [VARCHAR](255) NOT NULL,
[1] [DECIMAL](10,2) NULL,
[2] [DECIMAL](10,2) NULL,
[3] [DECIMAL](10,2) NULL,
[4] [DECIMAL](10,2) NULL,
[5] [DECIMAL](10,2) NULL,
[6] [DECIMAL](10,2) NULL,
[7] [DECIMAL](10,2) NULL,
[8] [DECIMAL](10,2) NULL,
[9] [DECIMAL](10,2) NULL,
[10] [DECIMAL](10,2) NULL,
[11] [DECIMAL](10,2) NULL,
[12] [DECIMAL](10,2) NULL,
[13] [DECIMAL](10,2) NULL,
[14] [DECIMAL](10,2) NULL,
[15] [DECIMAL](10,2) NULL,
[16] [DECIMAL](10,2) NULL,
[17] [DECIMAL](10,2) NULL,
[18] [DECIMAL](10,2) NULL,
[19] [DECIMAL](10,2) NULL,
[20] [DECIMAL](10,2) NULL,
[21] [DECIMAL](10,2) NULL,
[22] [DECIMAL](10,2) NULL,
[23] [DECIMAL](10,2) NULL,
[24] [DECIMAL](10,2) NULL,
[25] [DECIMAL](10,2) NULL,
[26] [DECIMAL](10,2) NULL,
[27] [DECIMAL](10,2) NULL,
[28] [DECIMAL](10,2) NULL,
[29] [DECIMAL](10,2) NULL,
[30] [DECIMAL](10,2) NULL,
[31] [DECIMAL](10,2) NULL
)
INSERT INTO #TablesInformation([database],[schema],[table])
SELECT DISTINCT [database_name],[schema],[table_name]
FROM DBA_Tables
ORDER BY [database_name],[schema],table_name
DECLARE @databaseName NVARCHAR(255)
DECLARE @schemaName NVARCHAR(64)
DECLARE @tableName NVARCHAR(255)
DECLARE @value DECIMAL(10,2)
DECLARE @dataTimestamp DATETIME
DECLARE @sqlCommand NVARCHAR(MAX)
IF(@targetParameter = 'row_count')
BEGIN
DECLARE TablesCursor CURSOR FOR
SELECT
[database_name],
[schema],
[table_name],
[row_count],
[data_collection_timestamp]
FROM DBA_Tables
ORDER BY [database_name],[schema],table_name
END
IF(@targetParameter = 'total_space_mb')
BEGIN
DECLARE TablesCursor CURSOR FOR
SELECT
[database_name],
[schema],
[table_name],
[total_space_mb],
[data_collection_timestamp]
FROM DBA_Tables
ORDER BY [database_name],[schema],table_name
END
IF(@targetParameter = 'used_space_mb')
BEGIN
DECLARE TablesCursor CURSOR FOR
SELECT
[database_name],
[schema],
[table_name],
[used_space_mb],
[data_collection_timestamp]
FROM DBA_Tables
ORDER BY [database_name],[schema],table_name
END
IF(@targetParameter = 'unused_space_mb')
BEGIN
DECLARE TablesCursor CURSOR FOR
SELECT
[database_name],
[schema],
[table_name],
[unused_space_mb],
[data_collection_timestamp]
FROM DBA_Tables
ORDER BY [database_name],[schema],table_name
END
OPEN TablesCursor
FETCH NEXT FROM TablesCursor INTO @databaseName,@schemaName,@tableName,@value,@dataTimestamp
WHILE(@@FETCH_STATUS = 0)
BEGIN
SET @sqlCommand = CONCAT('
UPDATE #TablesInformation
SET [',DAY(@dataTimestamp),'] = ',@value,'
WHERE [database] = ',CHAR(39),@databaseName,CHAR(39),'
AND [schema] = ',CHAR(39),@schemaName+CHAR(39),'
AND [table] = ',CHAR(39),@tableName+CHAR(39),'
')
EXEC(@sqlCommand)
FETCH NEXT FROM TablesCursor INTO @databaseName,@schemaName,@tableName,@value,@dataTimestamp
END
CLOSE TablesCursor
DEALLOCATE TablesCursor
IF(@targetParameter = 'row_count')
SELECT [database],
[schema],
[table],
CONVERT(INT,[1]) AS [1],
CONVERT(INT,[2]) AS [2],
CONVERT(INT,[3]) AS [3],
CONVERT(INT,[4]) AS [4],
CONVERT(INT,[5]) AS [5],
CONVERT(INT,[6]) AS [6],
CONVERT(INT,[7]) AS [7],
CONVERT(INT,[8]) AS [8],
CONVERT(INT,[9]) AS [9],
CONVERT(INT,[10]) AS [10],
CONVERT(INT,[11]) AS [11],
CONVERT(INT,[12]) AS [12],
CONVERT(INT,[13]) AS [13],
CONVERT(INT,[14]) AS [14],
CONVERT(INT,[15]) AS [15],
CONVERT(INT,[16]) AS [16],
CONVERT(INT,[17]) AS [17],
CONVERT(INT,[18]) AS [18],
CONVERT(INT,[19]) AS [19],
CONVERT(INT,[20]) AS [20],
CONVERT(INT,[21]) AS [21],
CONVERT(INT,[22]) AS [22],
CONVERT(INT,[23]) AS [23],
CONVERT(INT,[24]) AS [24],
CONVERT(INT,[25]) AS [25],
CONVERT(INT,[26]) AS [26],
CONVERT(INT,[27]) AS [27],
CONVERT(INT,[28]) AS [28],
CONVERT(INT,[29]) AS [29],
CONVERT(INT,[30]) AS [30],
CONVERT(INT,[31]) AS [31]
FROM #TablesInformation
ELSE
SELECT * FROM #TablesInformation
END
END
GO
Conclusione
- Puoi distribuire l'SP di raccolta dati in ogni istanza di SQL Server supportata e implementare un meccanismo di avviso nell'intero stack di istanze supportate.
- Se implementi un lavoro di agente che richiede queste informazioni con relativa frequenza, puoi rimanere aggiornato in termini di sapere come si comportano i tuoi dati durante il mese. Naturalmente, puoi andare ancora oltre e archiviare i dati raccolti mensilmente per avere un quadro ancora più ampio; dovresti apportare alcune modifiche al codice, ma ne varrebbe la pena.
- Assicurati di testare correttamente questo meccanismo in un ambiente sandbox e, quando pianifichi un'implementazione di produzione, assicurati di scegliere periodi di attività bassa.
- La raccolta di informazioni di questo tipo può aiutare a differenziare un DBA l'uno dall'altro. Probabilmente ci sono 3 strumenti di partito che possono fare la stessa cosa, e anche di più, ma non tutti hanno il budget per permetterselo. Spero che questo possa aiutare chiunque decida di usarlo nel proprio ambiente.