Как администратору баз данных мне часто приходится определять размеры баз данных как на уровне базы данных (агрегирование файла журнала и всех файлов данных, составляющих большую базу данных), так и на уровне файлов. Иногда эта информация мне требуется для всех баз данных, а иногда только для одной. Существует ряд возможностей сбора информации, но нет единого метода, который бы открыл все интересующие меня точки метаданных. Почему это важно? Когда мне потребуются эти сведения? Рассмотрим вопросы, интересующие каждого администратора:

  • Какие базы данных занимают больше всего места в хранилище данных?
  • Используются ли проценты в каких-либо файлах баз данных в качестве меры роста для событий автоматического увеличения?
  • Приходилось ли вам встречать ситуации, в которых журналы транзакций имеют неверное значение для размера базы данных?
  • Каковы ваши самые крупные журналы транзакций?
  • Имеют ли файлы данных для tempdb такой же размер и такие же параметры увеличения размера?
  • Приближаюсь ли я к заполнению каких-нибудь файлов данных или файлов журнала?
  • Размещены ли файлы данных (или журнала) на неверном диске?

Я могу перейти в среду SQL Server Management Studio (SSMS), раскрыть свойства базы данных и просмотреть сведения для отдельных файлов (размер при создании, логические и физические имена и параметры автоматического увеличения), но я не могу увидеть, насколько «полны» сейчас отдельные файлы.

Я могу запросить sys.database_files и получить похожую информацию, но все же не иметь нужных данных об использовании файлов, а по-прежнему видеть только результаты для единственной базы данных.

Аналогично, я могу запросить системное представление sys.master_files и получить идентичную информацию, представленную в sys.database_files, но вместо результатов только для одной базы данных будет возвращена информация для каждой базы данных на экземпляре SQL Server. По-прежнему отсутствует важный показатель использованного пространства, и это также означает, что нам неизвестно, сколько пространства остается в файле перед событием автоматического увеличения (если оно включено для данного файла).

Вы можете воспользоваться запросом t-sql, чтобы определить степень использования пространства в файле через функцию FILEPROPERTY (), но затем вам необходимо присоединить показатель к результатам других методов, упомянутых выше.

Еще один фактор, который необходимо учитывать: единицы измерения информации, возвращаемой разными средствами, не обязательно совпадают; часть ее представлена в мегабайтах, часть в страницах данных (по 8 Кбайт каждая). Такое рассогласование не упрощает задачу.

Все это заставляет искать единый способ получения информации, полезной администратору баз данных, такой как:

  • имя сервера;
  • имя базы данных;
  • имя файла (как логическое, так и физическое);
  • тип файла;
  • размер файла, использованное и свободное пространство;
  • единицы приращения (процент или фиксированное число Мбайт);
  • максимальный размер файла.

Поскольку не существует единого метода объединения этой информации, мне пришлось подготовить хранимую процедуру для ее сбора, и в этой статье я поделюсь своим опытом.

Параметры

Программный код обращается к некоторым из перечисленных выше источников: sys.database_files и функции FILEPROPERTY (). Остальное — чистая математика и преобразование страниц по 8 Кбайт (в некоторых случаях) в мегабайтные единицы. Хранимая процедура принимает два параметра:

  • @granularity: d | NULL — 'd' указывает на степень детализации базы данных и объединяет все метрики размеров для каждого файла данных (и отдельно для журнала транзакций) в одну строку;
  • @database_name: | NULL — если имя базы данных указано, то возвращаются только результаты для этой базы данных. В противном случае возвращаются результаты для всех баз данных на экземпляре.

Возвращаемые результаты

Следующие столбцы возвращаются при указании детализации на уровне базы данных:

  • server;
  • database_name;
  • db_size_mb;
  • db_free_mb;
  • db_used_mb;
  • data_size_mb;
  • data_free_mb;
  • data_used_mb;
  • data_used_pct;
  • log_size_mb;
  • log_free_mb;
  • log_used_mb;
  • log_used_pct.

Перечисленные ниже столбцы возвращаются при указании детализации на уровне файлов:

  • server;
  • database_name;
  • file_name;
  • physical_name;
  • file_type;
  • db_size_mb;
  • db_free_mb;
  • db_used_mb;
  • free_space_pct;
  • growth_units;
  • max_file_size_mb.

Программный код хранимой процедуры

В листинге приводится программный код для этой процедуры. Как и в случае с любыми программами, загружаемыми из Интернета, следует предварительно проверить его содержимое и производительность в непроизводственной среде. Данная команда выполняет только чтение, поэтому она не повлияет на значения и структуру каких-либо данных в ваших компьютерах, но я все равно всегда рекомендую понять программный код, полученный из внешних источников.

Тестовые результаты

Тестовые результаты приведены на экранах в зависимости от четырех возможных комбинаций параметров.

Параметры по умолчанию: детализация на уровне файлов и возврат результатов для всех баз данных (экран 1).

Подмножество возвращенных результатов для параметров по умолчанию
Экран 1. Подмножество возвращенных результатов для параметров по умолчанию

 

  • EXEC dbo.sp_sizing

Параметры: детализация на уровне базы данных и возврат результатов для всех баз данных (экран 2).

 

Подмножество результатов для детализации на уровне базы данных без фильтра для определенной базы данных
Экран 2. Подмножество результатов для детализации на уровне базы данных без фильтра для определенной базы данных

 

  • EXEC dbo.sp_sizing 'd', NULL

Параметры: детализация на уровне базы данных и возврат результатов только для одной базы данных с указанием базы данных (экран 3).

 

Результаты для детализации на уровне базы данных и фильтрации только для базы данных master
Экран 3. Результаты для детализации на уровне базы данных и фильтрации только для базы данных master
  • EXEC dbo.sp_sizing 'd', 'master'

Параметры: детализация на уровне файлов и возвращение результатов только для одной базы данных с указанием базы данных (экран 4).

 

Детализация на уровне файлов с фильтрацией только для базы данных master
Экран 4. Детализация на уровне файлов с фильтрацией только для базы данных master
  • EXEC dbo.sp_sizing NULL, 'master'

С помощью этого сценария администратор может быстро определить состояние файлов и баз данных, чтобы ответить на разнообразные вопросы, как приведенные в начале статьи, так и другие, которые регулярно возникают перед нами. Надеюсь, мой сценарий будет вам так же полезен, как и мне. В вашем распоряжении появился еще один инструмент, с помощью которого можно быстро получать точные ответы.

Листинг. Хранимая процедура сбора информации о базе данных и ее размере
USE [master]
GO

CREATE PROCEDURE [dbo].[sp_sizing] @granularity
        varchar(1) = NULL, @database_name sysname = NULL AS

/*-------------------------------------------------------------
Хранимая процедура dbo.sp_sizing
Автор Тим Форд, www.sqlcruise.com, www.thesqlagentman.com
Используйте без ограничений, но проверьте программный код
перед выполнением.
Ответственность за выполнение программного кода, загружаемого из Интернета, ложится на пользователя.
-------------------------------------------------------------*/

DECLARE @sql_command VARCHAR(5000)

CREATE TABLE #Results ([server] NVARCHAR(128), [database_name] NVARCHAR(128), [file_name] NVARCHAR(128), [physical_name] NVARCHAR(260), 
   [file_type] VARCHAR(4), [total_size_mb] INT, [available_space_mb]
   INT, [growth_units] VARCHAR(15), [max_file_size_mb] INT)

SELECT @sql_command = 
'USE [?] INSERT INTO #Results([server], [database_name], [file_name], [physical_name], 
[file_type], [total_size_mb], [available_space_mb], 
[growth_units], [max_file_size_mb]) 
SELECT CONVERT(nvarchar(128), SERVERPROPERTY(''Servername'')), DB_NAME(),
[name] AS [file_name], 
physical_name AS [physical_name], 
[file_type] = 
CASE type
WHEN 0 THEN ''Data''' 
+
           'WHEN 1 THEN ''Log'''
+
       'END,
[total_size_mb] =
CASE ceiling([size]/128) 
WHEN 0 THEN 1
ELSE ceiling([size]/128)
END,
[available_space_mb] = 
CASE ceiling([size]/128)
WHEN 0 THEN (1 - CAST(FILEPROPERTY([name], ''SpaceUsed''' + ')
as int) /128)
ELSE (([size]/128) - CAST(FILEPROPERTY([name], ''SpaceUsed''' + ')
as int) /128)
END,
[growth_units]  = 
CASE [is_percent_growth] 
WHEN 1 THEN CAST([growth] AS varchar(20)) + ''%'''
+
           'ELSE CAST([growth]/1024*8 AS varchar(20)) + ''Mb'''
+
       'END,
[max_file_size_mb] = 
CASE [max_size]
WHEN -1 THEN NULL
WHEN 268435456 THEN NULL
ELSE [max_size]/1024*8
END
FROM sys.database_files WITH (NOLOCK)
ORDER BY [file_type], [file_id]'

--Печать команды, применяемой ко всем базам данных
--PRINT @sql_command

--========================================
--ПРИМЕНЕНИЕ КОМАНДЫ К КАЖДОЙ БАЗЕ ДАННЫХ
--========================================
EXEC sp_MSforeachdb @sql_command

--=================================
--ВОЗВРАЩЕНИЕ РЕЗУЛЬТАТОВ

--Если значение @database_name равно NULL:
--=================================
IF @database_name IS NULL
   BEGIN
       IF @granularity= 'd' /* Database Scope */
           BEGIN
               SELECT 
                   T.[server], T.[database_name],
                   T.[total_size_mb] AS [db_size_mb],
                   T.[available_space_mb] AS [db_free_mb],
                   T.[used_space_mb] AS [db_used_mb],
                   D.[total_size_mb] AS [data_size_mb],
                   D.[available_space_mb] AS [data_free_mb],
                   D.[used_space_mb] AS [data_used_mb],
                   CEILING(CAST(D.[available_space_mb] AS decimal(10,1)) / D.[total_size_mb]*100) AS [data_free_pct],
                   L.[total_size_mb] AS [log_size_mb],
                   L.[available_space_mb] AS [log_free_mb],
                   L.[used_space_mb] AS [log_used_mb],
                   CEILING(CAST(L.[available_space_mb] AS decimal(10,1)) / L.[total_size_mb]*100) AS [log_free_pct]
               FROM 
                   (
                                           SELECT [server], [database_name],
                                                   SUM([total_size_mb]) AS [total_size_mb],
                                                   SUM([available_space_mb]) AS [available_space_mb],
                                                   SUM([total_size_mb]-[available_space_mb]) AS [used_space_mb] 
                                           FROM #Results
                                           GROUP BY [server], [database_name]
                   ) AS T
                   INNER JOIN 
                                           (
                                                   SELECT [server],
                                                                [database_name],
                                                                SUM([total_size_mb]) AS [total_size_mb],
                                                                SUM([available_space_mb]) AS [available_space_mb],
                                                                SUM([total_size_mb]-[available_space_mb]) AS [used_space_mb] 
                                                   FROM #Results
                                                   WHERE #Results.[file_type] = 'Data'
                                                   GROUP BY [server], [database_name]
                                           ) AS D ON T.[database_name] =
                                           D.[database_name]
                   INNER JOIN
                                           (
                                                   SELECT [server],
                                                                [database_name],
                                                                SUM([total_size_mb])
                                                                AS [total_size_mb],
                                                                SUM([available_space_mb])
                                                                AS [available_space_mb],
                                                                SUM([total_size_mb]-
                                                                [available_space_mb])
                                                                AS [used_space_mb] 
                                                   FROM #Results
                                                   WHERE #Results.[file_type] = 'Log'
                                                   GROUP BY [server], [database_name]
                                           ) AS L ON T.[database_name] =
                                           L.[database_name]
               ORDER BY D.[database_name]
           END
        ELSE /* File Scope */
                BEGIN
                        SELECT [server],
                                [database_name],
                                [file_name],
                                [physical_name],
                                [file_type],
                                [total_size_mb] AS [db_size_mb],
                                [available_space_mb] AS [db_free_mb],
                                CEILING(CAST([available_space_mb]
                                AS DECIMAL(10,1)) / [total_size_mb]*100)
                                AS [free_space_pct],
                                [growth_units],
                                [max_file_size_mb] /* AS [Grow Max Size (Mb)]  */
                        FROM #Results 
                        ORDER BY database_name, file_type, [file_name]
                END
        END

--=================================
--РЕЗУЛЬТАТы ДЛЯ БАЗЫ ДАННЫХ

--Если предоставлен параметр @database_name:
--=================================
ELSE
   BEGIN
       IF @granularity= 'd' /* Database Scope */
           BEGIN
               SELECT
                                        T.[server],
                                        T.[database_name],
                                        T.[total_size_mb] AS [db_size_mb],
                                        T.[available_space_mb] AS [db_free_mb],
                                        T.[used_space_mb] AS [db_used_mb],
                                        D.[total_size_mb] AS [data_size_mb],
                                        D.[available_space_mb] AS [data_free_mb],
                                        D.[used_space_mb] AS [data_used_mb],
                                        CEILING(CAST(D.[available_space_mb]
                                        AS DECIMAL(10,1)) / D.[total_size_mb]*100)
                                        AS [data_free_pct],
                                        L.[total_size_mb] AS [log_size_mb],
                                        L.[available_space_mb] AS [log_free_mb],
                                        L.[used_space_mb] AS [log_used_mb],
                                        CEILING(CAST(L.[available_space_mb]
                                        AS DECIMAL(10,1)) / L.[total_size_mb]*100)
                                        AS [log_free_pct]
               FROM 
                   (
                   SELECT [server], [database_name],
                       SUM([total_size_mb]) AS [total_size_mb],
                       SUM([available_space_mb]) AS [available_space_mb],
                       SUM([total_size_mb]-[available_space_mb])
                       AS [used_space_mb] 
                   FROM #Results
                   WHERE [database_name] = @database_name
                   GROUP BY [server], [database_name]
                   ) AS T
                   INNER JOIN 
                                           (
                                                   SELECT [server], [database_name],
                                                           SUM([total_size_mb])
                                                           AS [total_size_mb],
                                                           SUM([available_space_mb])
                                                           AS [available_space_mb],
                                                           SUM([total_size_mb]-[available_
                                                           space_mb]) AS [used_space_mb] 
                                                   FROM #Results
                                                   WHERE #Results.[file_type] = 'Data'
                                                           AND [database_name] =
                                                           @database_name
                                                   GROUP BY [server], [database_name]
                                           ) AS D ON T.[database_name] =
                                           D.[database_name]
                   INNER JOIN
                                           (
                                                   SELECT [server], [database_name],
                                                           SUM([total_size_mb])
                                                           AS [total_size_mb],
                                                           SUM([available_space_mb])
                                                           AS [available_space_mb],
                                                           SUM([total_size_mb]-[available_
                                                           space_mb]) AS [used_space_mb] 
                                                   FROM #Results
                                                   WHERE #Results.[file_type] = 'Log'
                                                           AND [database_name] =
                                                           @database_name
                                                   GROUP BY [server], [database_name]
                                           ) AS L ON T.[database_name] =
                                           L.[database_name]
               ORDER BY D.[database_name]
           END
       ELSE /* File Scope */
           BEGIN
               SELECT [server],
                                        [database_name],
                                        [file_name],
                                        [physical_name],
                                        [file_type],
                                        [total_size_mb] AS [db_size_mb],
                                        [available_space_mb] AS [db_free_mb],
                                        CEILING(CAST([available_space_mb]
                                        AS DECIMAL(10,1)) / [total_size_mb]*100)
                                        AS [free_space_pct],
                                        [growth_units],
                                        [max_file_size_mb] /* AS [Grow Max Size
                                        (Mb)]  */
               FROM #Results 
               WHERE [database_name] = @database_name
                           ORDER BY file_type, [file_name]
           END
   END
GO