Introducción
Es importante que un administrador de base de datos sepa cuándo no hay espacio en un disco. Por lo tanto, es mejor automatizar el proceso para que no lo hagan manualmente en cada servidor.
En este artículo, voy a describir cómo implementar la recopilación automática de datos diarios sobre unidades lógicas y archivos de bases de datos.
Solución
Algoritmo:
1. Crear tablas de almacenamiento de datos:
1.1. para archivos de base de datos:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [srv].[DBFile](
[DBFile_GUID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[Server] [nvarchar](255) NOT NULL,
[Name] [nvarchar](255) NOT NULL,
[Drive] [nvarchar](10) NOT NULL,
[Physical_Name] [nvarchar](255) NOT NULL,
[Ext] [nvarchar](255) NOT NULL,
[Growth] [int] NOT NULL,
[IsPercentGrowth] [int] NOT NULL,
[DB_ID] [int] NOT NULL,
[DB_Name] [nvarchar](255) NOT NULL,
[SizeMb] [float] NOT NULL,
[DiffSizeMb] [float] NOT NULL,
[InsertUTCDate] [datetime] NOT NULL,
[UpdateUTCdate] [datetime] NOT NULL,
[File_ID] [int] NOT NULL,
CONSTRAINT [PK_DBFile] PRIMARY KEY CLUSTERED
(
[DBFile_GUID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [srv].[DBFile] ADD CONSTRAINT [DF_DBFile_DBFile_GUID]
DEFAULT (newid()) FOR [DBFile_GUID]
GO
ALTER TABLE [srv].[DBFile] ADD CONSTRAINT [DF_DBFile_InsertUTCDate]
DEFAULT (getutcdate()) FOR [InsertUTCDate]
GO
ALTER TABLE [srv].[DBFile] ADD CONSTRAINT [DF_DBFile_UpdateUTCdate]
DEFAULT (getutcdate()) FOR [UpdateUTCdate]
GO 1.2. para unidades lógicas:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [srv].[Drivers](
[Driver_GUID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[Server] [nvarchar](255) NOT NULL,
[Name] [nvarchar](8) NOT NULL,
[TotalSpace] [float] NOT NULL,
[FreeSpace] [float] NOT NULL,
[DiffFreeSpace] [float] NOT NULL,
[InsertUTCDate] [datetime] NOT NULL,
[UpdateUTCdate] [datetime] NOT NULL,
CONSTRAINT [PK_Drivers] PRIMARY KEY CLUSTERED
(
[Driver_GUID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [srv].[Drivers] ADD CONSTRAINT [DF_Drivers_Driver_GUID]
DEFAULT (newid()) FOR [Driver_GUID]
GO
ALTER TABLE [srv].[Drivers] ADD CONSTRAINT [DF_Drivers_Server]
DEFAULT (@@servername) FOR [Server]
GO
ALTER TABLE [srv].[Drivers] ADD CONSTRAINT [DF_Drivers_TotalSpace]
DEFAULT ((0)) FOR [TotalSpace]
GO
ALTER TABLE [srv].[Drivers] ADD CONSTRAINT [DF_Drivers_FreeSpace]
DEFAULT ((0)) FOR [FreeSpace]
GO
ALTER TABLE [srv].[Drivers] ADD CONSTRAINT [DF_Drivers_DiffFreeSpace]
DEFAULT ((0)) FOR [DiffFreeSpace]
GO
ALTER TABLE [srv].[Drivers] ADD CONSTRAINT [DF_Drivers_InsertUTCDate]
DEFAULT (getutcdate()) FOR [InsertUTCDate]
GO
ALTER TABLE [srv].[Drivers] ADD CONSTRAINT [DF_Drivers_UpdateUTCdate]
DEFAULT (getutcdate()) FOR [UpdateUTCdate]
GO
Además, debe completar una tabla con unidades lógicas por adelantado de la siguiente manera:
Nombre del servidor:etiqueta de volumen
2. cree una vista necesaria para la recopilación de datos sobre los archivos de la base de datos:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE view [inf].[ServerDBFileInfo] as
SELECT @@Servername AS Server ,
File_id ,--file_id in a database. Its main value always equals 1
Type_desc ,--description of a file type
Name as [FileName] ,--logic file name in a database
LEFT(Physical_Name, 1) AS Drive ,--volume label where a database file is located
Physical_Name ,--a full name of a file in the operating system
RIGHT(physical_name, 3) AS Ext ,--file extension
Size as CountPage, --current file size in pages of 8 Kb
round((cast(Size*8 as float))/1024,3) as SizeMb, --file size in Mb
Growth, --growth
is_percent_growth, --growth in %
database_id,
DB_Name(database_id) as [DB_Name]
FROM sys.master_files--database_files
GO Aquí se utiliza la vista del sistema sys.master_files.
3. Cree un procedimiento almacenado que devuelva información en una unidad lógica:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create procedure [srv].[sp_DriveSpace]
@DrivePath varchar(1024) --device (it is possible to set a volume label 'C:')
, @TotalSpace float output --total volume in bytes
, @FreeSpace float output --free disk space in bytes
as
begin
DECLARE @fso int
, @Drive int
, @DriveName varchar(255)
, @Folder int
, @Drives int
, @source varchar(255)
, @desc varchar(255)
, @ret int
, @Object int
-- Create an object of a file system
exec @ret = sp_OACreate 'Scripting.FileSystemObject', @fso output
set @Object = @fso
if @ret != 0
goto ErrorInfo
-- Get a folder on the specified path
exec @ret = sp_OAmethod @fso, 'GetFolder', @Folder output, @DrivePath
set @Object = @fso
if @ret != 0
goto ErrorInfo
-- Get a device
exec @ret = sp_OAmethod @Folder, 'Drive', @Drive output
set @Object = @Folder
if @ret != 0
goto ErrorInfo
-- Determine the whole device storage space
exec @ret = sp_OAGetProperty @Drive, 'TotalSize', @TotalSpace output
set @Object = @Drive
if @ret != 0
goto ErrorInfo
-- Determine a free space on a disk
exec @ret = sp_OAGetProperty @Drive, 'AvailableSpace', @FreeSpace output
set @Object = @Drive
if @ret != 0
goto ErrorInfo
DestroyObjects:
if @Folder is not null
exec sp_OADestroy @Folder
if @Drive is not null
exec sp_OADestroy @Drive
if @fso is not null
exec sp_OADestroy @fso
return (@ret)
ErrorInfo:
exec sp_OAGetErrorInfo @Object, @source output, @desc output
print 'Source error: ' + isnull( @source, 'n/a' ) + char(13) + 'Description: ' + isnull( @desc, 'n/a' )
goto DestroyObjects;
end
GO Para obtener información detallada sobre este procedimiento, consulte el siguiente artículo:Espacio en disco en T-SQL.
4. Cree un procedimiento almacenado para la recopilación de datos:
4.1. para archivos de base de datos:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [srv].[MergeDBFileInfo]
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
;merge [srv].[DBFile] as f
using [inf].[ServerDBFileInfo] as ff
on f.File_ID=ff.File_ID and f.DB_ID=ff.[database_id] and f.[Server]=ff.[Server]
when matched then
update set UpdateUTcDate = getUTCDate()
,[Name] = ff.[FileName]
,[Drive] = ff.[Drive]
,[Physical_Name] = ff.[Physical_Name]
,[Ext] = ff.[Ext]
,[Growth] = ff.[Growth]
,[IsPercentGrowth] = ff.[is_percent_growth]
,[SizeMb] = ff.[SizeMb]
,[DiffSizeMb] = round(ff.[SizeMb]-f.[SizeMb],3)
when not matched by target then
insert (
[Server]
,[Name]
,[Drive]
,[Physical_Name]
,[Ext]
,[Growth]
,[IsPercentGrowth]
,[DB_ID]
,[DB_Name]
,[SizeMb]
,[File_ID]
,[DiffSizeMb]
)
values (
ff.[Server]
,ff.[FileName]
,ff.[Drive]
,ff.[Physical_Name]
,ff.[Ext]
,ff.[Growth]
,ff.[is_percent_growth]
,ff.[database_id]
,ff.[DB_Name]
,ff.[SizeMb]
,ff.[File_id]
,0
)
when not matched by source and f.[Server]example@sqldat.com@SERVERNAME then delete;
END
GO 4.2. para unidades lógicas:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [srv].[MergeDriverInfo]
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
declare @Drivers table (
[Server] nvarchar(255),
Name nvarchar(8),
TotalSpace float,
FreeSpace float,
DiffFreeSpace float NULL
);
insert into @Drivers (
[Server],
Name,
TotalSpace,
FreeSpace
)
select [Server],
Name,
TotalSpace,
FreeSpace
from srv.Drivers
where [Server]example@sqldat.com@SERVERNAME;
declare @TotalSpace float;
declare @FreeSpace float;
declare @DrivePath nvarchar(8);
while(exists(select top(1) 1 from @Drivers where DiffFreeSpace is null))
begin
select top(1)
@DrivePath=Name
from @Drivers
where DiffFreeSpace is null;
exec srv.sp_DriveSpace @DrivePath = @DrivePath
, @TotalSpace = @TotalSpace out
, @FreeSpace = @FreeSpace out;
update @Drivers
set example@sqldat.com
,example@sqldat.com
,DiffFreeSpace=case when FreeSpace>0 then round(example@sqldat.com,3) else 0 end
where example@sqldat.com;
end
;merge [srv].[Drivers] as d
using @Drivers as dd
on d.Name=dd.Name and d.[Server]=dd.[Server]
when matched then
update set UpdateUTcDate = getUTCDate()
,[TotalSpace] = dd.[TotalSpace]
,[FreeSpace] = dd.[FreeSpace]
,[DiffFreeSpace]= dd.[DiffFreeSpace];
END
GO 5. Cree vistas para la salida de datos:
5.1. para archivos de base de datos:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create view [srv].[vDBFiles] as
SELECT [DBFile_GUID]
,[Server]
,[Name]
,[Drive]
,[Physical_Name]
,[Ext]
,[Growth]
,[IsPercentGrowth]
,[DB_ID]
,[File_ID]
,[DB_Name]
,[SizeMb]
,[DiffSizeMb]
,round([SizeMb]/1024,3) as [SizeGb]
,round([DiffSizeMb]/1024,3) as [DiffSizeGb]
,round([SizeMb]/1024/1024,3) as [SizeTb]
,round([DiffSizeMb]/1024/1024,3) as [DiffSizeTb]
,round([DiffSizeMb]/([SizeMb]/100), 3) as [DiffSizePercent]
,[InsertUTCDate]
,[UpdateUTCdate]
FROM [srv].[DBFile];
GO 5.2. para discos lógicos:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create view [srv].[vDrivers] as
select
[Driver_GUID]
,[Server]
,[Name]
,[TotalSpace] as [TotalSpaceByte]
,[FreeSpace] as [FreeSpaceByte]
,[DiffFreeSpace] as [DiffFreeSpaceByte]
,round([TotalSpace]/1024, 3) as [TotalSpaceKb]
,round([FreeSpace]/1024, 3) as [FreeSpaceKb]
,round([DiffFreeSpace]/1024, 3) as [DiffFreeSpaceKb]
,round([TotalSpace]/1024/1024, 3) as [TotalSpaceMb]
,round([FreeSpace]/1024/1024, 3) as [FreeSpaceMb]
,round([DiffFreeSpace]/1024/1024, 3) as [DiffFreeSpaceMb]
,round([TotalSpace]/1024/1024/1024, 3) as [TotalSpaceGb]
,round([FreeSpace]/1024/1024/1024, 3) as [FreeSpaceGb]
,round([DiffFreeSpace]/1024/1024/1024, 3) as [DiffFreeSpaceGb]
,round([TotalSpace]/1024/1024/1024/1024, 3) as [TotalSpaceTb]
,round([FreeSpace]/1024/1024/1024/1024, 3) as [FreeSpaceTb]
,round([DiffFreeSpace]/1024/1024/1024/1024, 3) as [DiffFreeSpaceTb]
,round([FreeSpace]/([TotalSpace]/100), 3) as [FreeSpacePercent]
,round([DiffFreeSpace]/([TotalSpace]/100), 3) as [DiffFreeSpacePercent]
,[InsertUTCDate]
,[UpdateUTCdate]
FROM [srv].[Drivers]
GO 6. Cree una tarea en el Agente SQL Server y ejecútela una vez al día:
USE [DATABASE_NAME]; GO exec srv.MergeDBFileInfo; exec srv.MergeDriverInfo;
7. Recopile toda la salida de datos de los servidores. Puede hacerlo utilizando el Agente SQL Server, por ejemplo.
8. Cree un procedimiento almacenado para generar un informe y enviarlo a los administradores. Dado que es posible implementarlo de diferentes maneras, lo consideraré en este ejemplo en particular:
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [srv].[GetHTMLTableShortInfoDrivers]
@body nvarchar(max) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
declare @tbl table (
Driver_GUID uniqueidentifier
,[Name] nvarchar(255)
,[TotalSpaceGb] float
,[FreeSpaceGb] float
,[DiffFreeSpaceMb] float
,[FreeSpacePercent] float
,[DiffFreeSpacePercent] float
,UpdateUTCDate datetime
,[Server] nvarchar(255)
,ID int identity(1,1)
);
declare
@Driver_GUID uniqueidentifier
,@Name nvarchar(255)
,@TotalSpaceGb float
,@FreeSpaceGb float
,@DiffFreeSpaceMb float
,@FreeSpacePercent float
,@DiffFreeSpacePercent float
,@UpdateUTCDate datetime
,@Server nvarchar(255)
,@ID int;
insert into @tbl(
Driver_GUID
,[Name]
,[TotalSpaceGb]
,[FreeSpaceGb]
,[DiffFreeSpaceMb]
,[FreeSpacePercent]
,[DiffFreeSpacePercent]
,UpdateUTCDate
,[Server]
)
select Driver_GUID
,[Name]
,[TotalSpaceGb]
,[FreeSpaceGb]
,[DiffFreeSpaceMb]
,[FreeSpacePercent]
,[DiffFreeSpacePercent]
,UpdateUTCDate
,[Server]
from srv.vDrivers
where [DiffFreeSpacePercent]<=-5
or [FreeSpacePercent]<=15
order by [Server] asc, [Name] asc;
if(exists(select top(1) 1 from @tbl))
begin
set @body='When analyzing I have got the data storage devices that either have free disk space less than 15%, or free space decreases over 5% a day:<br><br>'+'<TABLE BORDER=5>';
set @example@sqldat.com+'<TR>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+'№ p/p';
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+'GUID';
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+'SEVER';
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+'TOM';
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+'VOLUME, GB.';
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+'FREE, GB.';
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+'FREE SPACE CHANGE, MB.';
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+'FREE, %';
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+'FREE SPACE CHANGE, %';
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+'UTC DETECTION TIME';
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'</TR>';
while((select top 1 1 from @tbl)>0)
begin
set @example@sqldat.com+'<TR>';
select top 1
@Driver_GUID = Driver_GUID
,@Name = Name
,@TotalSpaceGb = TotalSpaceGb
,@FreeSpaceGb = FreeSpaceGb
,@DiffFreeSpaceMb = DiffFreeSpaceMb
,@FreeSpacePercent = FreeSpacePercent
,@DiffFreeSpacePercent = DiffFreeSpacePercent
,@UpdateUTCDate = UpdateUTCDate
,@Server = [Server]
,@ID = [ID]
from @tbl;
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+cast(@ID as nvarchar(max));
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+cast(@Driver_GUID as nvarchar(255));
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+coalesce(@Server,'');
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+coalesce(@Name,'');
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+cast(@TotalSpaceGb as nvarchar(255));
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+cast(@FreeSpaceGb as nvarchar(255));
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+cast(@DiffFreeSpaceMb as nvarchar(255));
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+cast(@FreeSpacePercent as nvarchar(255));
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+cast(@DiffFreeSpacePercent as nvarchar(255));
set @example@sqldat.com+'</TD>';
set @example@sqldat.com+'<TD>';
set @example@sqldat.com+rep.GetDateFormat(@UpdateUTCDate, default)+' '+rep.GetTimeFormat(@UpdateUTCDate, default);
set @example@sqldat.com+'</TD>';
delete from @tbl
where example@sqldat.com;
set @example@sqldat.com+'</TR>';
end
set @example@sqldat.com+'</TABLE>';
set @example@sqldat.com+'<br><br>';
To get the detailed information, refer to the view SRV.srv.vDrivers<br><br>
To view the information on database files, refer to the view DATABASE_NAME.srv.vDBFiles';
end
END
GO Este procedimiento almacenado genera un informe HTML sobre las unidades lógicas que tienen menos del 15 % de espacio libre en el disco o que disminuyen más del 5 % al día. Este último muestra una extraña actividad de registros, lo que significa que alguien almacena demasiada información en este disco con mucha frecuencia. Puede ocurrir por las siguientes razones:
- Es hora de extender un disco;
- Es necesario eliminar los archivos no utilizados en una unidad lógica;
- Borrar y reducir archivos de registro, así como archivos de información y otras tablas.
Solución
En este artículo, analicé un ejemplo de implementación de un sistema de recopilación automática diaria de datos sobre unidades locales y archivos de bases de datos. Esta información permite saber de antemano qué disco tiene menos espacio libre, así como qué archivos de la base de datos crecen drásticamente. Permite evitar un caso en el que no hay espacio en un disco y averiguar la razón por la que un proceso ocupa mucho espacio en un disco.
Lea también:
Recopilación automática de datos de cambios en el esquema de la base de datos en MS SQL Server
Recopilación automática de datos sobre tareas completadas en MS SQL Server