Като администратори на база данни на SQL Server, ние винаги се грижим за едно от най-важните неща за бизнеса, данните. В някои случаи приложенията могат да станат доста сложни и в крайна сметка ще получите много таблици на база данни, разпръснати около вашия SQL Server екземпляр(и). Това може да доведе до някои неудобства, като например:
- Да знаете как се държат данните ви всеки ден по отношение на тенденциите на растеж (пространство и/или количество редове).
- Да знаете какви таблици на базата данни изискват (или ще изискват) конкретна/различна стратегия за съхраняване на данните, защото расте твърде бързо.
- Да знаете кои от вашите таблици на база данни заемат твърде много място, което вероятно води до ограничения за съхранение.
Поради важността на тези подробности създадох няколко съхранени процедури, които могат да бъдат от голяма помощ на всеки SQL Server DBA, който би искал да следи информация относно таблиците на базата данни в неговата/нейната среда. Повярвайте ми, един от тях е много готин.
Първоначални съображения
- Уверете се, че акаунтът, изпълняващ тази съхранена процедура, има достатъчно привилегии. Вероятно бихте могли да започнете със sysadmin и след това да преминете възможно най-подробно, за да сте сигурни, че потребителят има минималните привилегии, необходими за правилното функциониране на SP.
- Обектите на базата данни (таблица на базата данни и съхранена процедура) ще бъдат създадени в базата данни, избрана в момента на изпълнение на скрипта, така че избирайте внимателно.
- Скриптът е изработен по начин, по който може да бъде изпълнен няколко пъти, без да ви бъде изведена грешка. За съхранената процедура използвах израз „CREATE OR ALTER PROCEDURE“, наличен от SQL Server 2016 SP1. Ето защо не се учудвайте, ако не работи гладко в по-ранна версия.
- Чувствайте се свободни да промените имената на създадените обекти на базата данни.
- Обърнете внимание на параметрите на съхранената процедура, която събира необработените данни. Те могат да бъдат от решаващо значение в мощна стратегия за събиране на данни за визуализиране на тенденциите.
Как да използвам съхранените процедури?
- Копирайте и поставете T-SQL кода (наличен в тази статия).
- Първият SP очаква 2 параметъра:
- @persistData:„Y“, ако DBA иска да запише изхода в целева таблица, и „N“, ако DBA иска да види изхода директно.
- @truncateTable:„Y“, за да се съкрати първо таблицата, преди да се съхранят заснетите данни, и „N“, ако текущите данни се съхраняват в таблицата. Имайте предвид, че стойността на този параметър е без значение, ако стойността на параметъра @persistData е „N“.
- Вторият SP очаква 1 параметър:
- @targetParameter:Името на колоната, която ще се използва за транспониране на събраната информация.
Представени полета и тяхното значение
- име на база_данни: името на базата данни, където се намира таблицата.
- схема: името на схемата, където се намира таблицата.
- име_на таблица: заместителя за името на таблицата.
- брой_редове: броя на редовете, които таблицата има в момента.
- total_space_mb: броят мегабайтове, разпределени за таблицата.
- used_space_mb: броят мегабайтове, които действително се използват от таблицата.
- unused_space_mb: броят мегабайтове, които таблицата не използва.
- created_date: датата/часа, когато е създадена таблицата.
- data_collection_timestamp: видимо само ако „Y“ се предава на параметъра @persistData. Използва се, за да се знае кога SP е бил изпълнен и информацията е била успешно запазена в таблицата DBA_Tables.
Тестове за изпълнение
Ще демонстрирам няколко изпълнения на съхранените процедури:
/* Показва информацията за таблиците за всички потребителски бази данни */
EXEC GetTablesData @persistData = 'N',@truncateTable = 'N'
/* Запазете информацията от таблиците на базата данни и поискайте целевата таблица, като първо съкратите целевата таблица */
EXEC GetTablesData @persistData = 'Y',@truncateTable = 'Y'
SELECT * FROM DBA_Tables
Странични заявки
*Запитване за преглед на таблиците на базата данни, сортирани от най-големия брой редове до най-ниския.
SELECT * FROM DBA_Tables ORDER BY row_count DESC;
*Запитване за преглед на таблиците на базата данни, сортирани от най-голямото общо пространство до най-ниското.
SELECT * FROM DBA_Tables ORDER BY total_space_mb DESC;
*Запитване за преглед на таблиците на базата данни, сортирани от най-голямото използвано пространство до най-ниското.
SELECT * FROM DBA_Tables ORDER BY used_space_mb DESC;
*Запитване за преглед на таблиците на базата данни, сортирани от най-голямото неизползвано пространство до най-ниското.
SELECT * FROM DBA_Tables ORDER BY unused_space_mb DESC;
*Запитване за преглед на таблиците на базата данни, сортирани по дата на създаване, от най-новите до най-старите.
SELECT * FROM DBA_Tables ORDER BY created_date DESC;
Ето пълен код на Съхранената процедура, която улавя информацията от таблиците на базата данни:
*В самото начало на скрипта ще видите стойността по подразбиране, която запаметената процедура приема, ако не се подаде стойност за всеки параметър.
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
До този момент информацията изглежда малко суха, но нека променя това възприятие с представянето на допълнителна Съхранена процедура. Основната му цел е да транспонира информацията, събрана в целевата таблица, която служи като източник за отчети за тенденциите.
Ето как можете да изпълните съхранената процедура:
*За демонстрационни цели вмъкнах ръчни записи в целевата таблица с име t1, за да симулирам обичайното ми изпълнение на Съхранена процедура.
*Наборът от резултати е малко широк, така че ще направя няколко екранни снимки, за да покажа пълния резултат.
EXEC TransposeTablesInformation @targetParmeter = 'row_count'
Основни изводи
- Ако автоматизирате изпълнението на скрипта, който попълва целевата таблица, можете веднага да забележите дали нещо се обърка с него или с вашите данни. Разгледайте данните за таблицата „t1“ и колоната „15“. Можете да видите NULL там, което е направено нарочно, за да ви покаже нещо, което може да се случи.
- С този вид изглед можете да видите особено поведение за най-важните/критични таблици на база данни.
- В дадения пример избрах полето „row_count“ на целевата таблица, но можете да изберете всяко друго числово поле като параметър и да получите същия формат на таблицата, но с различни данни.
- Не се притеснявайте, ако посочите невалиден параметър, съхранената процедура ще ви предупреди и ще спре изпълнението й.
Ето пълен код на Съхранената процедура, който транспонира информацията от целевата таблица:
*В самото начало на скрипта ще видите стойността по подразбиране, която запаметената процедура приема, ако не се подаде стойност за всеки параметър.
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
Заключение
- Можете да разгърнете SP за събиране на данни във всеки екземпляр на SQL Server под вашата поддръжка и да внедрите механизъм за предупреждение в целия си пакет от поддържани екземпляри.
- Ако внедрите задача на агент, която прави заявки за тази информация относително често, можете да останете на върха на играта по отношение на това как да знаете как се държат данните ви през месеца. Разбира се, можете да отидете още по-далеч и да съхранявате месечните събирани данни, за да имате още по-голяма картина; ще трябва да направите някои промени в кода, но би си струвало напълно.
- Уверете се, че сте тествали правилно този механизъм в среда на пясъчна среда и когато планирате внедряване в производство, не забравяйте да изберете периоди с ниска активност.
- Събирането на информация от този тип може да помогне за разграничаването на DBA един от друг. Вероятно има 3 партии инструмента, които могат да направят едно и също нещо, и дори повече, но не всеки има бюджет да си го позволи. Надявам се, че това може да помогне на всеки, който реши да го използва в своята среда.