Jako administratorzy baz danych SQL Server zawsze dbamy o jedną z najważniejszych rzeczy dla biznesu, czyli o dane. W niektórych przypadkach aplikacje mogą stać się dość złożone i w rezultacie otrzymujesz mnóstwo tabel baz danych rozsianych po instancjach SQL Server. Może to prowadzić do kilku niedogodności, takich jak:
- Wiedza, jak Twoje dane zachowują się każdego dnia, pod względem trendów wzrostu (przestrzeń i/lub liczba wierszy).
- Wiedza, jakie tabele bazy danych wymagają (lub będą wymagać) określonej/innej strategii przechowywania danych, ponieważ rośnie ona zbyt szybko.
- Wiedza, które z tabel bazy danych zajmują zbyt dużo miejsca, co może prowadzić do ograniczeń pamięci.
Ze względu na wagę tych szczegółów stworzyłem kilka procedur składowanych, które mogą być bardzo pomocne dla każdego administratora baz danych SQL Server, który chciałby śledzić informacje dotyczące tabel bazy danych w swoim środowisku. Zaufaj mi, jeden z nich jest bardzo fajny.
Rozważania wstępne
- Upewnij się, że konto wykonujące tę procedurę składowaną ma wystarczające uprawnienia. Prawdopodobnie możesz zacząć od sysadmin, a następnie przejść tak szczegółowo, jak to możliwe, aby upewnić się, że użytkownik ma minimalne uprawnienia wymagane do prawidłowego działania SP.
- Obiekty bazy danych (tabela bazy danych i procedura składowana) zostaną utworzone wewnątrz bazy danych wybranej w czasie wykonywania skryptu, więc wybieraj ostrożnie.
- Skrypt jest tak skonstruowany, że można go wykonać kilka razy bez wyrzucenia błędu. W przypadku procedury składowanej użyłem instrukcji „CREATE OR ALTER PROCEDURE”, dostępnej od SQL Server 2016 SP1. Dlatego nie zdziw się, jeśli nie działa płynnie we wcześniejszej wersji.
- Możesz zmienić nazwy utworzonych obiektów bazy danych.
- Zwróć uwagę na parametry procedury składowanej, która gromadzi nieprzetworzone dane. Mogą mieć kluczowe znaczenie w potężnej strategii gromadzenia danych do wizualizacji trendów.
Jak korzystać z zapisanych procedur?
- Skopiuj i wklej kod T-SQL (dostępny w tym artykule).
- Pierwszy SP oczekuje 2 parametrów:
- @persistData:„Y”, jeśli administrator danych chce zapisać dane wyjściowe w tabeli docelowej, i „N”, jeśli administrator danych chce bezpośrednio wyświetlić dane wyjściowe.
- @truncateTable:„Y”, aby najpierw obciąć tabelę przed zapisaniem przechwyconych danych, i „N”, jeśli bieżące dane są przechowywane w tabeli. Pamiętaj, że wartość tego parametru nie ma znaczenia, jeśli wartość parametru @persistData to „N”.
- Drugi SP oczekuje 1 parametru:
- @targetParameter:nazwa kolumny, która ma być użyta do transpozycji zebranych informacji.
Prezentowane pola i ich znaczenie
- nazwa_bazy danych: nazwa bazy danych, w której znajduje się tabela.
- schemat: nazwa schematu, w którym znajduje się tabela.
- nazwa_tabeli: symbol zastępczy nazwy tabeli.
- liczba wierszy: liczba wierszy, które aktualnie ma tabela.
- całkowita_przestrzeń_mb: liczba megabajtów przydzielonych dla tabeli.
- used_space_mb: liczba megabajtów faktycznie używanych przez tabelę.
- unused_space_mb: liczba megabajtów, których tabela nie używa.
- data_utworzenia: data/godzina utworzenia tabeli.
- data_collection_timestamp: widoczne tylko po przekazaniu „Y” do parametru @persistData. Jest używany, aby wiedzieć, kiedy SP został wykonany i informacje zostały pomyślnie zapisane w tabeli DBA_Tables.
Testy wykonania
Pokażę kilka wykonań procedur składowanych:
/* Wyświetl informacje o tabelach dla wszystkich baz danych użytkowników */
EXEC GetTablesData @persistData = 'N',@truncateTable = 'N'
/* Zachowaj informacje z tabel bazy danych i wykonaj zapytanie do tabeli docelowej, najpierw obcinając tabelę docelową */
EXEC GetTablesData @persistData = 'Y',@truncateTable = 'Y'
SELECT * FROM DBA_Tables
Zapytania poboczne
*Zapytanie, aby wyświetlić tabele bazy danych posortowane od największej liczby wierszy do najmniejszej.
SELECT * FROM DBA_Tables ORDER BY row_count DESC;
*Zapytanie, aby wyświetlić tabele bazy danych posortowane od największej całkowitej przestrzeni do najmniejszej.
SELECT * FROM DBA_Tables ORDER BY total_space_mb DESC;
*Zapytanie, aby wyświetlić tabele bazy danych posortowane od największego do najmniejszego miejsca.
SELECT * FROM DBA_Tables ORDER BY used_space_mb DESC;
*Zapytanie, aby wyświetlić tabele bazy danych posortowane od największego niewykorzystanego miejsca do najmniejszego.
SELECT * FROM DBA_Tables ORDER BY unused_space_mb DESC;
*Zapytanie, aby wyświetlić tabele bazy danych posortowane według daty utworzenia, od najnowszych do najstarszych.
SELECT * FROM DBA_Tables ORDER BY created_date DESC;
Oto pełny kod procedury składowanej, który przechwytuje informacje z tabel bazy danych:
*Na samym początku skryptu zobaczysz domyślną wartość, którą przyjmuje procedura składowana, jeśli nie przekazano żadnej wartości dla każdego parametru.
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
Do tego momentu informacje wydają się nieco suche, ale pozwólcie, że zmienię to postrzeganie, prezentując uzupełniającą procedurę przechowywaną. Jego głównym celem jest transpozycja informacji zebranych w tabeli docelowej, która służy jako źródło raportów trendów.
Oto jak możesz wykonać procedurę składowaną:
*W celach demonstracyjnych wstawiłem ręcznie rekordy do tabeli docelowej o nazwie t1, aby zasymulować moje zwykłe wykonanie procedury zapisanej.
*Zestaw wyników jest nieco szeroki, więc zrobię kilka zrzutów ekranu, aby pokazać pełny wynik.
EXEC TransposeTablesInformation @targetParmeter = 'row_count'
Kluczowe dania na wynos
- Jeśli zautomatyzujesz wykonanie skryptu, który zapełnia tabelę docelową, możesz natychmiast zauważyć, czy coś poszło nie tak z nim lub z danymi. Spójrz na dane dla tabeli „t1” i kolumny „15”. Możesz tam zobaczyć NULL, który został zrobiony celowo, aby pokazać ci coś, co może się wydarzyć.
- Dzięki temu widokowi możesz zobaczyć szczególne zachowanie najważniejszych/krytycznych tabel bazy danych.
- W podanym przykładzie wybrałem pole „row_count” tabeli docelowej, ale możesz wybrać dowolne inne pole numeryczne jako parametr i uzyskać ten sam format tabeli, ale z innymi danymi.
- Nie martw się, jeśli określisz nieprawidłowy parametr, procedura składowana ostrzeże Cię i zatrzyma jej wykonywanie.
Oto kompletny kod procedury składowanej, która transponuje informacje z tabeli docelowej:
*Na samym początku skryptu zobaczysz domyślną wartość, którą przyjmuje procedura składowana, jeśli nie przekazano żadnej wartości dla każdego parametru.
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
Wniosek
- Możesz wdrożyć SP do zbierania danych w każdej instancji SQL Server pod twoim wsparciem i zaimplementować mechanizm ostrzegania w całym stosie obsługiwanych instancji.
- Jeśli zaimplementujesz zadanie agenta, które zadaje te informacje stosunkowo często, możesz być na bieżąco z tym, jak zachowują się Twoje dane w ciągu miesiąca. Oczywiście możesz pójść jeszcze dalej i przechowywać gromadzone co miesiąc dane, aby mieć jeszcze większy obraz; musiałbyś wprowadzić kilka poprawek w kodzie, ale byłoby to całkowicie tego warte.
- Upewnij się, że prawidłowo przetestowałeś ten mechanizm w środowisku piaskownicy, a planując wdrożenie produkcyjne, wybierz okresy niskiej aktywności.
- Zbieranie informacji tego typu może pomóc w odróżnieniu DBA od siebie. Prawdopodobnie istnieją narzędzia innych firm, które mogą zrobić to samo, a nawet więcej, ale nie każdy ma na to budżet. Mam nadzieję, że pomoże to każdemu, kto zdecyduje się go używać w swoim środowisku.