martes, 27 de julio de 2010

Restaurar multiples bases de datos a la vez

En ocasiones hace falta realizar restauraciones masivas de ficheros de backup. Situaciones como resolver una catástrofe o realizar pruebas de migración, pueden requerirnos restaurar 40, 100 bases de datos…y aqui es donde entra este script.

Para el proyecto en el que estoy ahora involucrado, estoy realizando pruebas que involucran directamente restauraciones de más de 50 BBDD y claro…los informáticos no nos caracterizamos por nuesta pasión a las tareas repetitivas, asique…¿mejor que lo haga otro, no?. Pues bien, ese otro será nuestro SQL Server Smile

Los parámetros de inicio del siguiente script son:

  1. dbs: Cursor con los nombres de ficheros de backup a restaurar (si se llama mibbdd.bak, pues mibbdd)
  2. pathFisicoMDF: Ruta donde acabarán los ficheros de datos
  3. pathFIsicoLDF: Ruta donde acabarán los ficheros de log
  4. pathToBak: Ruta donde buscar los ficheros de backup .bak

Solo una cosa mas,

/*
Enrique Catala Bañuls
*/
declare @path nvarchar(max)
DECLARE @dbName sysname
DECLARE @logicalName sysname
declare @physicalName nvarchar(max)
declare @fileName nvarchar(max)
declare @sSql nvarchar(max)
declare @fileType char(1)
declare @pathToBak nvarchar(max)
declare @pathFisicoMDF nvarchar(max)
declare @pathFisicoLDF nvarchar(max)

/*********************************************************************************************/
/*********************************************************************************************/
--
-- FILL HERE DATA NEEDED TO THE RESTORNG PROCESS
--
DECLARE dbs CURSOR READ_ONLY
-- bases de datos restaurándose
FOR select 'bdeap'



select @pathFisicoMDF = N'e:\datos\',
@pathFisicoLDF = N'd:\logs\',
@pathToBak = 'E:\fulldiaria\'
/*********************************************************************************************/
/*********************************************************************************************/

create table #tmp
(
LogicalName sysname
,PhysicalName nvarchar(max)
,Type char(1)
,FileGroupName sysname NULL
,Size numeric(20,0)
,MaxSize numeric(20,0),
Fileid tinyint,
CreateLSN numeric(25,0),
DropLSN numeric(25, 0),
UniqueID uniqueidentifier,
ReadOnlyLSN numeric(25,0) NULL,
ReadWriteLSN numeric(25,0) NULL,
BackupSizeInBytes bigint,
SourceBlocSize int,
FileGroupId int,
LogGroupGUID uniqueidentifier NULL,
DifferentialBaseLSN numeric(25,0) NULL,
DifferentialBaseGUID uniqueidentifier,
IsReadOnly bit,
IsPresent bit,
TDEthumbprint varbinary(32) NULL
)

OPEN dbs
FETCH NEXT FROM dbs INTO @dbName
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN

SELECT @sSql = '',
@path = 'restore filelistonly from disk = N'''+@pathToBak+@dbName+'.bak'''

--PRINT @path

insert #tmp
EXEC (@path)

--select * from #tmp

DECLARE dbs2 CURSOR READ_ONLY
FOR select logicalName,physicalName,type from #tmp
OPEN dbs2

FETCH NEXT FROM dbs2 INTO @logicalName,@physicalName,@fileType
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
set @fileName= substring(@physicalName,
2+len(@physicalName)-charindex('\',reverse(@physicalName)),
charindex('\',reverse(@physicalName)))

if @fileType = 'D'
set @sSql = @sSql+' MOVE N'''+@logicalName+''' TO N'''+@pathFisicoMDF+@fileName+''','
else
set @sSql = @sSql + ' MOVE N'''+@logicalName+''' TO N'''+@pathFisicoLDF+@fileName+''','

--print @logicalName
--print @pathFisicoMDF
--print @sSql

END
FETCH NEXT FROM dbs2 INTO @logicalName,@physicalName,@fileType
END

CLOSE dbs2
DEALLOCATE dbs2

--print @dbName
--print @pathToBak
--print @sSql
--SET @sSql = 'RESTORE DATABASE ['+@dbName+'] FROM DISK = N'''+ @pathToBak +'\' +@dbName + '.bak''
SET @sSql = '
RESTORE DATABASE ['+@dbName+'] FROM DISK = N'''+ @pathToBak +'\' +@dbName + '.bak''
WITH FILE = 1, '
+@sSql+
'
RECOVERY, NOUNLOAD, REPLACE, STATS = 10'
--print @sSql
exec(@sSql)

truncate table #tmp
END
FETCH NEXT FROM dbs INTO @dbName
END

CLOSE dbs
DEALLOCATE dbs
GO
drop table #tmp





jueves, 8 de julio de 2010

Transformar los resultados de SSUA en tupla

Si alguna vez has usado SQL Server Upgrade Advisor para analizar el estado actual de un servidor de SQL Server de cara a una migración, habrás podido notar que la herramienta en sí, aunque funciona como debe, peca en algunos aspectos…con solo tener que analizar más de 1 instancia ya te das cuenta de lo que te digo Smile

Bueno, pues si a alguien (como es mi caso) no le apetece tener que lidiar con los ficheros DE.xml de resultado exportados a .csv que genera la herramienta por defecto…que sepa que se puede muy facilmente hacer uno un “parser” de dicho XML para hacer con la información lo que mejor se quiera.

Para esto lo único que tenemos que hacer es generarnos el .xsd de resultado (Visual Studio lo hace automático por nosotros), leer el XML a un DataSet y luego lanzar esta “sencilla” query LINQ2DataSet Smile

 
 var q = from ia in ds.Tables["ItemAttribute"].AsEnumerable()
join ias in ds.Tables["ItemAttributes"].AsEnumerable()
on ia.Field<int>("ItemAttributes_id")
equals ias.Field<int>("ItemAttributes_id")
join item in ds.Tables["Item"].AsEnumerable()
on ias.Field<int>("Item_id")
equals item.Field<int>("Item_id")
join r in ds.Tables["Report"].AsEnumerable()
on item.Field<Nullable<byte>>("ItemId")
equals r.Field<Nullable<byte>>("ItemId")
join iss in ds.Tables["Issue"].AsEnumerable()
on r.Field<Nullable<byte>>("IssueId")
equals iss.Field<Nullable<byte>>("IssueId")
select new
{
ItemAttributesId = ia.Field<int>("ItemAttributes_id"),
Name = ia.Field<string>("Name"),
Value = ia.Field<string>("Value"),
Issue = iss.Field<string>("IssueDescription"),
IssueText = iss.Field<string>("IssueText"),
IssueType = iss.Field<string>("IssueType")
};



Ahora solo te queda usar el objeto “q” para añadirle o toquetearle lo que quieras…una pista?…¿Qué tal que tu parser sea capaz de darte estimaciones temporales basado en el tipo de “Issue”? Smile


Salu2!

martes, 6 de julio de 2010

Averiguar conexiones externas hacia nuestro SQL Server

Últimamente estoy participando en bastantes proyectos de migración de SQL Server 2000 a SQL Server 2008 R2. Independientemente de la arquitectura existente en cada cliente, siempre hemos de conocer la topologia de consultas que se lanzan y sobre todo QUIEN las lanza.

En un proyecto de migración nunca podemos dejar cabos sueltos y menos cuando uno de los cabos sueltos puede llevar a que aplicaciones (sean o no críticas) no funcionen.

La opción más sensata siempre es crear una traza de profiler para SQL Server 2000, que nos capture durante un tiempo prudencialmente amplio y significativo, la actividad de nuestro servidor

¿Qué nos interesa conocer?

  1. La consulta que ha sido lanzada (posteriormente lo utilizaremos para que SSUA analice si hay patrones conflictivos)
  2. El hostname desde donde se lanza
  3. El login utilizado
  4. El nombre de la aplicación
  5. La BBDD sobre la que se está ejecutando la consulta

 

Dicho esto, nos podemos hacer una idea de los eventos e información que necesitamos capturar en SQL Server profiler…y ahora viene la parte divertida…la explotación de esos datos Smile

-- Create table with data
--
CREATE TABLE [dbo].ExternalConnectionAnalysis(
[ServerName] [nvarchar](256) NOT NULL,
[databaseid] [int] NULL,
[applicationname] [nvarchar](256) NULL,
[hostname] [nvarchar](256) NULL,
[loginname] [nvarchar](256) NULL,
queries_executed bigint not null
) ON [PRIMARY]

GO

INSERT into dbo.ExternalConnectionAnalysis(ServerName, databaseid,
          applicationname, hostname, loginname,queries_executed)
SELECT 'atlante3' AS ServerName , databaseid, applicationname,
       hostname, loginname,COUNT(*)
FROM ::fn_trace_gettable('path_to_file.trc', default)
GROUP BY databaseid, applicationname, hostname, loginname
go



Tenemos esa opción que evidentemente te lee el fichero de traza .trc (y los que vengan detrás en caso de haberse creado con multiples ficheros) y agrupa por la información que queremos, o podemos optar por una forma algo más rebuscada y eficiente para procesar los datos como esta otra:



declare @srvname sysname = 'my_server'
declare @trcpath varchar(max) = 'path_to_file.trc'
;

with existent_data as(
select ServerName, databaseid,
applicationname,hostname,loginname
from dbo.ExternalConnectionAnalisys
where ServerName = @srvname
),
trc as (
SELECT @srvname AS ServerName
, trc.databaseid, trc.applicationname,
trc.hostname, trc.loginname
FROM ::fn_trace_gettable(@trcpath, default) trc
)
INSERT into dbo.ExternalConnectionAnalysis(ServerName, databaseid,
applicationname, hostname, loginname,queries_executed)
SELECT @srvname AS ServerName , trc.databaseid, trc.applicationname,
trc.hostname, trc.loginname ,COUNT(*)
FROM trc left join existent_data ed on
(trc.DatabaseID = ed.databaseid
and trc.ApplicationName = ed.applicationname
and trc.HostName = ed.hostname
and trc.LoginName = ed.loginname
)
where ed.databaseid is null or ed.applicationname is null or
ed.hostname is null or ed.loginname is null
GROUP BY trc.databaseid, trc.applicationname, trc.hostname, trc.loginname



La gran ventaja de utilizar la consulta anterior radica principalmente en que es una consulta que solo añadirá las nuevas filas con información que le vayamos proponiendo. Es decir, que como es normal, tendremos ficheros de traza a traves del tiempo (cada dia previsiblemente tendremos .trc nuevos) y los podremos procesar independiemente de tener todos los ficheros de traza y procesarlos de golpe.



Evidentemente es una gran ventaja…pero otra ventaja oculta es si miras un poco más alla de la simple consulta y te das cuenta de que al utilizar el left join y el group by, nuestro SQL Server ha generado un plan de ejecución eficiente mediante un fantástico MERGE JOIN



image



 



Queda a tu disposición probar la consulta sin el left join, mediante un cruce de los de toda la vida y ver que ocurre…y sobre todo el tiempo que tarda, por culpa del super LOOP JOIN que te mete Smile



 



Salu2!