Mostrando entradas con la etiqueta SQL 2008. Mostrar todas las entradas
Mostrando entradas con la etiqueta SQL 2008. Mostrar todas las entradas

miércoles, 9 de noviembre de 2011

Recompilando vistas en SQL Server

Esta vez haré un post mas corto de lo habitual. Ayer a un colega se le presentó una situación, que por un cambio en varias cosas de su motor debía recompilar una vista. Esto es facil utilizando

EXEC SP_REFRESHVIEW [miVista];
El problema es que ocurre cuando en lugar de una vista son cientas y potencialmente divididas en varias bases de datos. La solución que le brindé es un TSQL que le generase un script con todos los refresh para que despues pueda ejecutarlo y guardarlo como documentación. Para esto me valí de un procedimiento no documentado del sql que es el sp_MSforeachdb.
Les comparto como quedó el script final

-- =============================================
-- Create:        Andrés Aiello
-- Create date: 09/11/11
-- Description: Como recompilar todas las vistas
-- =============================================

sp_MSforeachdb '
SELECT ''USE [?]
GO 
EXEC SP_REFRESHVIEW [''+NAME+''];
GO'' FROM SYSOBJECTS WHERE TYPE = ''V'' AND NOT ''?'' IN (''master'', ''tempdb'', ''msdb'', ''model'');
'
 
More: http://msdn.microsoft.com/en-us/library/ms187821.aspx

martes, 25 de octubre de 2011

Aplicar filtros al sp_who2 en SQL Server

Muchas veces usamos el sp_who2 para ver las conexiones activas y determinada información sobre esta, pero esto es comodo si los resultados son pocos, en el caso de ser muchos registros, cientos o miles esto ya no es aplicable.
Para estos casos deseariamos poder aplicar un filtro al sp_who2 pero esto lamentablemente no es posible. Lo que veremos en este ejemplo es como hacer un script, que podemos tener siempre a mano, que se encargue de aplicar los filtros.
El primer paso será crear una tabla temporal donde gaurdará los datos devueltos por el sp_who2. Como siempre primero controlo si la tabla ya existe para que no me arroje error al ejecutar.


-- ============================================= -- Create:        Andrés Aiello -- Create date: 25/10/11 -- Description: Filtrar los resultados del sp_who2 -- ============================================= -- Creo la tabla temporaria donde guardaré la salida del sp_who IF NOT OBJECT_ID('tempdb.dbo.#sp_who2') IS NULL DROP TABLE dbo.#sp_who2 CREATE TABLE #sp_who2     (SPID INT,     Status VARCHAR(1000) NULL,     Login SYSNAME NULL,     HostName SYSNAME NULL,     BlkBy SYSNAME NULL,     DBName SYSNAME NULL,     Command VARCHAR(1000) NULL,     CPUTime INT NULL,     DiskIO INT NULL,     LastBatch VARCHAR(1000) NULL,     ProgramName VARCHAR(1000) NULL,     SPID2 INT,     REQUESTID INT) GO

Una vez creada guardo en ella la salida del sp_who2


-- Inserto los valores INSERT INTO #sp_who2 EXEC sp_who2 GO


El paso siguiente es solamente hacer un select sobre dicha tabla, pero ahora aplicando filtro u orden, es decir lo mismo que haría en cualquier tabla. Por ejemplo filtrar solo las conexiones de mi usuario ordenadas por fecha.


SELECT *   FROM #sp_who2 -- Pongo el filtro que deseo  WHERE Login = 'AAIELLO' -- O el orden que deseo!!! ORDER BY LastBatch DESC GO


Por último elimino las estructuras creadas para no dejar "sucia" la base.


 -- Elimino las estructuras temporarias DROP TABLE #sp_who2 GO
 

Espero que les sirva!!! dejo a continuación el script copleto para que sea comodo de guardar.

 
-- ============================================= -- Create:        Andrés Aiello -- Create date: 25/10/11 -- Description: Filtrar los resultados del sp_who2 -- ============================================= -- Creo la tabla temporaria donde guardaré la salida del sp_who IF NOT OBJECT_ID('tempdb.dbo.#sp_who2') IS NULL DROP TABLE dbo.#sp_who2 CREATE TABLE #sp_who2     (SPID INT,     Status VARCHAR(1000) NULL,     Login SYSNAME NULL,     HostName SYSNAME NULL,     BlkBy SYSNAME NULL,     DBName SYSNAME NULL,     Command VARCHAR(1000) NULL,     CPUTime INT NULL,     DiskIO INT NULL,     LastBatch VARCHAR(1000) NULL,     ProgramName VARCHAR(1000) NULL,     SPID2 INT,     REQUESTID INT) GO -- Inserto los valores INSERT INTO #sp_who2 EXEC sp_who2 GO SELECT *   FROM #sp_who2 -- Pongo el filtro que deseo  WHERE Login <> 'sa' -- O el orden que deseo!!! ORDER BY LastBatch DESC GO -- Elimino las estructuras temporarias DROP TABLE #sp_who2 GO
 


domingo, 23 de octubre de 2011

Screencast: Local Server Groups en SQL

Hola!!! para los que les pareció interesante el artículo de como ejecutar un mismo script sql sobre varios servidores, acá dejo un screencast explicando de forma mas didáctica como hacer esto.
Saludos!

martes, 4 de octubre de 2011

Setea el check_policy para los logins que no lo tengan

Esta vez voy a hacer un post bien simple pero útil. Hay dos opciones muy interesantes al crear logins que son el check_policy y el check_expiration. La primera nos permite controlar que las contraseñas ingresadas por los usuarios cumplan con los requisitos de seguridad de complejidad de contraseña mientras que la segunda habilita que las contraseñas expiran una vez vencido cierto tiempo. Es importante notar (error muy común) que el check_policy se evalúa AL MOMENTO de setear la contraseña. Esto quiere decir que si creo un usuario, le pongo como contraseña '1' y luego habilito el check_poclicy, dicho usuario va a poder conectarse sin problema. Pero sin en cambio creo el usuario, habilito el check_policy y luego pongo como contraseña '1' no me dejará definirla por no cumplir los requisitos de seguridad. En el primer caso la contraseña '1' le permitirá loguearse pero al momento de querer cambiarla no podrá poner como contraseña '2' sino que ahi si deberá cumplir las reglas definidas.

Una vez terminada la introducción y dejando los conceptos en claro vamos a los bifes. La idea es hacer una consulta que me liste todos los usuarios que no cumplen esta buena práctica y poder cambiar su estado. El script que armé es el siguiente:

-- =============================================
-- Author: Andrés Aiello
-- Create date: 02/05/2011
-- Setea el check_policy para los logins que no lo tengan
-- =============================================

DECLARE @SQLQuery varchar(1000)
DECLARE @LoginName varchar(255)

DECLARE cLogins CURSOR FORWARD_ONLY FOR
SELECT name
--,is_policy_checked,'ALTER LOGIN [' + name + '] WITH CHECK_POLICY=ON' Query
FROM sys.sql_logins
WHERE is_policy_checked = 0
/* Ignoro los logins que considere que por algun motivo no deben ser tenidos en cuenta */
AND NOT name IN ('xxxxxxxxxxx')
ORDER BY NAME

OPEN cLogins
FETCH NEXT FROM cLogins INTO @LoginName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @SQLQuery = 'ALTER LOGIN [' + @LoginName + '] WITH CHECK_POLICY=ON'
PRINT @SQLQuery
FETCH NEXT FROM cLogins INTO @LoginName
END
CLOSE cLogins
DEALLOCATE cLogins



Este script como pueden ver no ejecuta el código sino que lo saca por la salida. Esto se debe a que una buena idea sería previo a hacer los cambios documentarlos salvando el script y luego ejecutarlo.

En este ejemplo fue para el check_policy, pero si quisieran hacer lo mismo para el expiration es igual solamente que el campo a filtrar es is_expiration_checked.
Andrés

Controlar CMDShell en SQL

Todos saben que no es deseable tener el CMDShell activado en los servidores, pero hay veces que uno llega a un server y ya se encuentra activado y ahí surge la pregunta de que hacer. La primer medida es cambiar las credenciales para que se ejecute con permisos mínimos, pero no hablaremos de eso ahora. Junto con esto es importante identificar donde se utiliza visto que los permisos dependerán de las llamadas que haga. Como no podemos recorrer base por base viendo si se utiliza, les dejo una consulta que hice para este fin

EXEC sp_MSForeachdb 'SELECT ''?'' DBName, text FROM [?].SYS.SYSCOMMENTS WHERE text LIKE ''%XP_CMDSHELL%'''
Esta consulta retorna todos los lugares donde se utiliza el cmdshell. Hay algunas referencias del sistema que las deberán ignorar.
Saludos!

nota: recordar que la función sp_MSForeachdb es una función no documentada

miércoles, 21 de septiembre de 2011

Renombrar filename de una base SQL

Hoy quiero contar un problema que me presentó un amigo hace unos días y me parece que a muchos les puede pasar. La persona en cuestión muchas veces había renombrado una base de datos, o había cambiado su nombre lógico, pero las veces que tenía que cambiar el nombre físico el procedimiento era:
1) Deatach de la base
2) Renombrado del file
3) Atach de la base

Es procedimiento es un poco extremo y aprovecharé para explicarles como hacerlo de forma mas simple.
El primer paso es asegurarme cuales son los files que tengo definidos en la base, en el ejemplo consideraremos la base "Librería". Para obtener esta información ejecutamos:

SELECT name, physical_name AS CurrentLocation, state_desc
FROM sys.master_files
WHERE database_id = DB_ID('Libreria');


Ahí obtendremos como resultado tres columnas, Name (nombre lógico), CurrentLocation (donde se encuentra actualmente el file) y state_desc (estado del file). Es importante que se recuerde que el nombre lógico es como se refenciará internamente al archivo, mientras que el físico es su nombre real. Puedo cambiar uno sin cambiar el otro.
La consulta dará como mínimo dos registros de salida, una para el archivo mdf (data) y otra para el ldf  (log).

Supongamos que quiero cambiar ambos archivos y renombrarlos LibreriaDATA y LibreriaLOG respectivamente, entonces debo poner offline la base y luego ejecutar la siguiente sentencia:
-- Pongo a la base offline
ALTER DATABASE Libreria SET OFFLINE WITH ROLLBACK IMMEDIATE

-- Cambio los nombres a donde apuntan. El NAME es el nombre logico que quiero modificar y el FILENAME es el NUEVO nombre fisico
ALTER DATABASE Libreria
MODIFY FILE (NAME = Libreria, FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL2008R2\MSSQL\DATA\LibreriaDATA.mdf' )
ALTER DATABASE Libreria
MODIFY FILE (NAME = Libreria_log, FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL2008R2\MSSQL\DATA\LibreriaLOG.LDF' )
-- Muevo los archivos a nivel sistema operativo
Si todo está ok pongo la base nuevamente en línea. Es importante que noten que puedo hacer el alter apuntando a un archivo inexistente, el control se hará al momento de poner la base online.
-- Una vez renombrados la pongo nuevamente en linea
ALTER DATABASE Libreria SET ONLINE

-- Controlo que todo quede como yo quería
SELECT name, physical_name AS CurrentLocation, state_desc
FROM sys.master_files
Si todo está ok ya terminé mi trabajo. En caso de que haya por error apuntado a un archivo inexistente (ej. me faltó renombrar un archivo) la base no se pondrá online arrojando el siguiente error:

Msg 5120, Level 16, State 101, Line 1
Unable to open the physical file

Es una buena práctica al finalizar ejecutar nuevamente la consulta inicial y corroborar que realmente todo quedó como queríamos que quedara.
Espero que les sirva!

lunes, 12 de septiembre de 2011

Screencast: CTE recursivas en SQL Server

Mi primer Screencast!!! Espero que les guste mi primer screencast, trata sobre CTE recursivas (ver post anterior)

      

miércoles, 24 de agosto de 2011

Restore de CDC en SQL 2008

El cdc es una feature muy buena del SQL2008 visto que de forma sencilla nos permite llevar un registro de los cambios en una tabla, tanto sea para cuestiones de auditoría, procesamientos parciales de información, etc.
Así mismo, cuando se trabaja con cdc es normal que se deseen probar, testear, desarrollar procedimientos que utilicen esta información y naturalmente no queremos que accedan al ambiente productivo hasta estar testeadas. Aquí es cuando nos topamos que si hacemos un restore de una base con cdc en otra instancia no aparecen las tablas correspondientes. Para evitar esto debemos incluir la opción WITH KEEP_CDC al momento de hacer restore. No se si será poco usada, pero no se asusten si no es coloreada en el Managment Studio, funciona a la perfección.
El código debería quedar algo así:

RESTORE DATABASE [AIELLO_DB]
FROM DISK = N'D:\Backup\[AIELLO_DB][SQL2008].bak'
WITH
KEEP_CDC,
FILE = 1,
MOVE N'AIELLO_DB'
TO N'D:\MSSQL10.MSSQLSERVER\MSSQL\DATA\AIELLO_DB.mdf',
MOVE N'AIELLO_DB_log'
TO N'D:\MSSQL10.MSSQLSERVER\MSSQL\DATA\AIELLO_DB_log.ldf',
MOVE N'AIELLO_DB_cdc'
TO N'D:\MSSQL10.MSSQLSERVER\MSSQL\DATA\AIELLO_DB_cdc.ndf',
NOUNLOAD,
REPLACE,
STATS = 10

Con esto tendrán la información para poder explotarla, pero no se activará la captura de datos. Si desean que esto ocurra hay que incluir los jobs correspondientes con sys.sp_cdc_add_job aplicando tanto 'capture' como 'cleanup'.
Saludos,
Andrés
Para mas información:

jueves, 11 de agosto de 2011

SQL2008: Entendiendo el Waitresource

Introduccion
Al ver los xml de bloqueos o deadlocks muchas veces aparecen números casi inentendibles, y uno de los importantes es el WAITRESOURCE. Este número codifica que objeto de la base de datos está participando en la pelea por el bloqueo. El recurso puede principalmente ser:
  • Una tabla
  • Una página
  • Una clave
Puede ser algunas cosas mas pero para simplificar el análisis nos enfocaremos en estos.
Manos a la obra
Lo primero que debemos hacer es identificar cual de las tres cosas es, lo cual es fácil leyendo el texto de wait resourse que puede ser:
“OBJECT: 19:1275867612:10”
“PAGE: 12:1:79868773”
“KEY: 12:397371816017920 (760119e06bff)”
En el primer caso es cuando se trata de una tabla. Para este caso (y para los siguientes) lo primero que debemos hacer es analizar el primer número antes del separador (:) . Este número en todos los casos indica el id de la base de datos, por lo tanto debo hacer la siguiente consulta para obtenerla:
SELECT * FROM sys.sysdatabases WHERE Dbid=19
Lo cual me da por resultado Mibase. Recordemos esto porque es común a los tres casos.
Ahora siguiendo con el análisis del primer caso la información brindada se debe analizar de la siguiente manera:

OBJECT: dbId:ObjectId:IndexId
Con la salvedad de que indexId vale 0 cuando se trata del heap y 1 cuando se trata de un índice cluster. En otro caso figurará el número del índice.
Para obtener esta información en un formato útil debería hacer las siguientes consultas:
-- Nota: debo estar situado en MiBase
SELECT OBJECT_NAME(1275867612) 
-- O bien
SELECT * FROM MiBase.sys.all_objects WHERE object_id = 1275867612
-- Y para buscar el índice en caso de ser mayor que 1:
SELECT * FROM MiBase.sys.indexes WHERE object_id=1275867612
De esta forma ya sabemos que table y que índice participaba en el bloqueo.
Para el caso 2 (página) comenzaremos averiguando la base de la misma forma y la siguiente información nos viene como:
PAGE: dbId:FileId:PageId
Si no tenemos nuestra base con varios archivos sino todo en uno del primary el segundo campo siempre será 1.
Para analizar el último debemos obtener la información de la página (79868773 en el ejemplo). Para esto tenemos dos formas:

DBCC TRACEON ( 3604 )
DBCC PAGE (12,1,79868773)
o
DBCC PAGE (12 , 1, 79868773) WITH TABLERESULTS,NO_INFOMSGS
En el primer caso es necesario el traceon porque sino no podemos ver la salida, la cual saldrá en modo texto. En el segundo no es necesario visto que la salida la veremos en modo tabla.
Sea cual fuera el caso que elegimos debemos buscar el renglon (o registro) que tenga la siguiente información:

m_objId (AllocUnitId.idObj) = 1051306955 m_indexId (AllocUnitId.idInd) = 7
y con esto obtengo el ObjectId para poder continuar mi análisis como en el caso anterior.
Por último nos queda el caso del índice (KEY: 12:397371816017920 (760119e06bff)). En este caso debemos leerlo como:

KEY: dbId:hObjecto (hash)
El hash puede ser ignorado, visto que no tenemos función para transformarlo (es la gracia de los hash!), así que nos quedaremos con el hObj. Este número se encuentra almacenado en la msdb así que lo podemos utilizar para calcular el objetcId:

SELECT * FROM MiBase.sys.partitions
WHERE hobt_id = 397371816017920
De esta consulta obtenemos el object_id y podemos hacer nuestro análisis de siempre.

lunes, 18 de julio de 2011

Cluster desde powershell


Cuando se trabaja con SQL en entornos de alta disponibilidad es muy comun trabajar con windows en cluster y tener que lidiar con varias tareas que involucran tanto sea al sql como al cluster.
La forma tradicional de como hacer esto es con el comando Cluster.exe, pero para esto deberíamos habilitar el cmd shell lo cual nunca es recomendable.
Por suerte desde 2008 tenemos de forma nativa en los jobs incorporar rutinas de powershell, pero hasta ese entonces la única salida era seguir acudiendo al cluster.exe.
Esto ha cambiado con la salida de Windows Server 2008 R2 (no confundir con SQL Server 2008 R2!!!), donde se ha incorporado un modulo de cluster al powershell que hace muy comoda su adminstración.
Para utilizar este modulo primero debemos entrar a nuestra consola de powershell y ejecutar el siguiente comando:

Import-Module FailoverClusters

Con esto se incorporarán las funciones de cluster y podremos utilizarlas. En mi caso me ha servido porque en conjunto con SQL 2008 puedo llamarlas desde un job de sql configurandolo con rutina powershell y

esto es flexible y seguro.

Les adjunto un link de como pasar las tareas de Cluster.exe a powershell:

Como usarlo: http://technet.microsoft.com/en-us/library/ee619751%28WS.10%29.aspx

Mapeo: http://technet.microsoft.com/en-us/library/ee619744%28WS.10%29.aspx

====================================================================


When working with SQL in high availability is very common to work over windows cluster and dealing with various tasks involving both sql server and the windows cluster.
The traditional way of how to do this is with the command Cluster.exe, but for this should enable the cmd shell it is never recommended.
Luckily since 2008 We have powershell natively in jobs routines, but the only way out was to keep going to the cluster.exe.
This has changed with the release of Windows Server 2008 R2 (not to be confused with SQL Server 2008 R2!!!), which has a powershell module of the cluster that makes it very comfortable its management.
To use this module must first enter our powershell console and run the following command:

Import-Module FailoverClusters

This will incorporate the functions of cluster and we use them. In my case I have served it in conjunction with SQL 2008 I can call them from a sql job of setting it powershell with routine and that is

flexible and secure.

I attached a link of how to change tasks Cluster.exe to powershell:

How to use: http://technet.microsoft.com/en-us/library/ee619751%28WS.10%29.aspx
Mapping: http://technet.microsoft.com/en-us/library/ee619744%28WS.10%29.aspx

jueves, 7 de julio de 2011

Restore de FILESTREAM

El filestream es una funcionalidad nueva del SQL2008 que a veces es discutida pero a mi ver es muy buena en muchos casos. En esta oportunidad me gustaría hablar sobre un caso interesante y es como hacer para restorear una base que tiene campos filestream pero que no ha sido diseñado pensando en esto. ¿A que me refiero? Uno puede diseñar la base desde un principio alojando las tablas que contienen archivos en filegroups diferentes para asegurarse que en un restore parcial no afecten, pero no siempre las cosas nacen en este orden, muchas veces las aplicaciones vienen de hace tiempo y "llegan" al sql 2008 y en la tabla que se encuentra el binario tambien se encuentra otra información indispensable para el funcionamiento del sisetema. En estos casos no restorear el filegroup en cuestión es lo mismo que no restorear nada. Para estos casos el filestream es una buena solución visto que en cierto modo puedo hacer un filegroup "vertical".
Supongamos una tabla "cliente" que tenga los campos id, nombre, apellido, Documento, siendo el último un VARBINARY(MAX). Si esta tabla la pongo en un filegroup diferente y hago un restore del resto obtendría error al realizar la siguiente consulta:

SELECT id, nombre, apellido FROM Cliente


Restore con PARTIAL

Ahora supongamos que mi campo base tiene definido FILESTREAM y el campo Documento se encuentra albergado de esa forma. En este caso ante un problema por el cual deseo recuperar mi base podría hacer lo siguiente:

RESTORE DATABASE [AIELLO_DBA]
FROM DISK = N'D:\Prueba_fs.bak'
WITH REPLACE, PARTIAL, RECOVERY


Y ahora la consulta antes mencionada funcionaría sin problemas, solamente obtendría un error si intento acceder al campo documento, como sería con la siguiente consulta:

SELECT id, nombre, apellido, Documento FROM Cliente

Msg 670, Level 16, State 1, Line 2
Large object (LOB) data for table "dbo.Cliente" resides on an offline filegroup ("AIELLO_DBA_FS") that cannot be accessed.

Lo que logré es poner mi base en linea en un tiempo mucho menor. Pero lamentablemente para recuperar todo debo volver a hacer el restore full en otro momento.


Restore operativo por partes
No todo está perdido!! como dice el dicho, hecha la ley hecha la trampa, entonces lo que hice es lo siguiente.

En mi backup nocturno hago lo siguiente:

-- =============================================
-- Author: Andrés Aiello
-- Create date: 07/07/2011
-- Backup full poniendo readonly el filestream
-- =============================================

ALTER DATABASE [AIELLO_DBA] MODIFY FILEGROUP [AIELLO_DBA_FS] READONLY
BACKUP DATABASE [AIELLO_DBA]
TO DISK = N'D:\Prueba_fs_readonly.bak'
WITH NOFORMAT, NOINIT, NAME = N'PRUEBA FS', SKIP, NOREWIND, NOUNLOAD, STATS = 10
ALTER DATABASE [AIELLO_DBA] MODIFY FILEGROUP [AIELLO_DBA_FS] READWRITE


Por lo tanto el filegroup de los documentos es guardado como readonly. Al momento de hacer restore lo puedo hacer de dos formas:

--============== MODO 1 - Full
-- 7:38 min

RESTORE DATABASE [AIELLO_DBA]
FROM DISK = N'D:\Prueba_fs_readonly.bak'
WITH REPLACE, RECOVERY
ALTER DATABASE [AIELLO_DBA] MODIFY FILEGROUP [AIELLO_DBA_FS] READWRITE


Esta forma hace un restore completo de la forma tradicional, dejando el servicio bajo el tiempo que dure el restore (en una base donde el mayor porcentaje del espacio son archivos esto puede crecer mucho).
Y el plan b...

-- =============================================
-- Author: Andrés Aiello
-- Create date: 07/07/2011
-- Restore inteligente
-- =============================================
--============== MODO 2 - Sin los binarios
-- 0:46 
RESTORE DATABASE [AIELLO_DBA]
FROM DISK = N'D:\Prueba_fs_readonly.bak'
WITH REPLACE, PARTIAL, RECOVERY
ALTER DATABASE [AIELLO_DBA] SET RECOVERY FULL WITH NO_WAIT
-- Base online sin los binarios!!!
-- Recuperar todo...
-- 8:30
RESTORE DATABASE [AIELLO_DBA]
FILEGROUP='AIELLO_DBA_FS'
FROM DISK = N'D:\Prueba_fs_readonly.bak'
WITH REPLACE, RECOVERY
ALTER DATABASE [AIELLO_DBA] MODIFY FILEGROUP [AIELLO_DBA_FS] READWRITE
De esta forma la base en solo 45 segundos se encuentra operativa!!! y mientras se va utilizando se va recuperando en background los documentos del otro filegroup. Para evitar inconsistencias mientras se recupera esa parte el filegroup se mantiene en readonly.
Andrés

martes, 25 de enero de 2011

Sp_who en Oracle

En el tiempo que llevo trabajando con SQL Server creo que el comando que mas veces ejecuté es el sp_who o su versión extendida sp_who2. Este comando cuando se está administrando una base es muy comodo por su sencillez y nos da un buen resumen de que está ocurriendo en el servidor. Claramente cuando hay alguna situación fuera de lo normal esto es solo la puerta de entrada para despues controlar las DMVs, los contadores del sistema operativo, etc, pero siempre es un buen comienzo. Muchas personas les pasa que acostumbradas a ambientes SQL Server se sientan frente a un sistema Oracle y buscan algo equivalente y no lo encuentran, así que veamos como sería una consulta equivalente en Oracle.
No contamos con un stored procedure prearmado que nos brinde la información, pero al igual que en SQL Server, esta información se nutre de las sesiones, así que consultaremos la V_$SESSION al igual que el sp_who original.
En este caso para que les sea mas comodo acomodé los campos para que queden igual que en la versión original.

SELECT sid, status, Username, terminal, blocking_session_status, schemaname, a.name Command_Action, logon_time, program
FROM SYS.V_$SESSION s
INNER JOIN AUDIT_ACTIONS a ON s.command=a.action;

Como podrán ver aparte de consultar la vista de sesiones hice un join con la tabla AUDIT_ACTIONS que es la que contiene las descripciones de cada evento para que sepamos que está haciendo la session.
Lo que recomiendo es guardar esta consulta en un script a mano y poco a poco irse internalizando mas con la SYS.V_$SESSION visto que tiene mucho para darnos. Quien no esté acostumbrado se va a sorprender al encontrar muchos mas campos que en su equivalente de SQL Server.
Pueden encontrar mas detalles en:
http://download.oracle.com/docs/cd/B19306_01/server.102/b14237/dynviews_2088.htm

Por último si lo que se desea es hacer algo mas semejante al sp_who2 y no tienen ganas de internalizarse en los demas campos, puede hacerse una función que nos retorne los resultados de la consulta. Esta función sería:

-- Creacion de funcion que retorna como resultado una tabla
CREATE OR REPLACE function sp_who2 return sp_who2_tab PIPELINED
IS
CURSOR cur0
IS
SELECT sid, status, Username, terminal, blocking_session_status, schemaname, a.name Command_Action, logon_time, program
FROM SYS.V_$SESSION s
INNER JOIN AUDIT_ACTIONS a ON s.command=a.action;

out_rec sp_who2_rec := sp_who2_rec(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
BEGIN
OPEN cur0;
LOOP
FETCH cur0 INTO out_rec.sid, out_rec.status, out_rec.Username,
out_rec.terminal, out_rec.blocking_session_status, out_rec.schemaname, out_rec.Command_Action, out_rec.logon_time, out_rec.program;
EXIT WHEN cur0%NOTFOUND;
PIPE ROW (out_rec);
END LOOP;
CLOSE cur0;
RETURN;

END;

Y como sucede con las funciones que retornan tablas en Oracle, la forma de utilizarla sería:
SELECT * FROM TABLE(sp_who2);
Claramente a esto hay que sumarle la creación de los objetos necesarios (record y table).
Espero que les haya servido!

lunes, 17 de enero de 2011

Drop and Create

Muchas veces al armar los scripts figuran creación de objetos, y obviamente es deseable que nuestro script no falle por mas que dicho objeto ya se encuentre creado. En el caso de los stored procedures, en Oracle, tenemos la opción de CREATE OR REPLACE, pero cuando nos manejamos con tablas esta opción, lamentablemente, no existe ni en Oracle ni en SQL Server.
Para eliminar y crear una tabla desde cero en SQL Server es bastante conocido el método (de echo en la versión 2008 en adelante ya lo tenemos a un click del managment). El código es:

-- Prueba 1: Controlo si existe la tabla, si es necesario la elimino y la vuelvo a crear
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.prueba_creacion') AND type in (N'U'))
DROP TABLE dbo.prueba_creacion

CREATE TABLE prueba_creacion(clave INT);

De esta forma controlamos si la tabla existe, y en caso afirmativo la eliminamos. Luego podemos proceder a crearla sin problema.
Otra es la historia cuando la tabla es una tabla temporaria. Lo intuitivo sería ahcer lo mismo con el nombre de la tabla temporaria, pero esto arrojaría error. ¿porqué? porque la tabla temporaria se encuentra alojada en la tempdb. El código para poder realizar lo mismo con tablas temporarias sería:

-- Prueba 2: Controlo si existe la tabla temporaria, si es necesario la elimino y la vuelvo a crear
IF NOT OBJECT_ID('tempdb.dbo.#prueba_creacion_temp') IS NULL
DROP TABLE dbo.#prueba_creacion_temp

CREATE TABLE #prueba_creacion_temp(clave INT);

Pueden ver que se código no importa cuantas veces se ejecute nunca arrojará error.
Por último es interesante resaltar que en oracle no podemos realizar ninguno de estos controles debido a que el parser arrojará error la primera vez al hacer un DROP TABLE de una tabla que aún no existe. Por este motivo la forma de realizar lo mismo en Oracle sería:

-- Prueba 3: Controlo si existe la tabla en oracle, si es necesario la elimino y la vuelvo a crear

BEGIN
EXECUTE IMMEDIATE 'DROP TABLE prueba_creacion';
EXCEPTION WHEN OTHERS THEN NULL;
END;

CREATE TABLE prueba_creacion(clave INT);

De esta forma se ejecutará siempre el DROP TABLE y arrojará error de sintaxis cuando no exista la tabla, pero el error será en el sql dinámico y no en todo el script, y estará capturado por el WHEN OTHERS, haciendo que sea transparente para el resto de la ejecución.

lunes, 3 de enero de 2011

RML para SQL 2008 - Parte 1

A quien no le ha pasado de tener un sistema de base de datos, hacer unos cambios y no estar seguro si dichos cambios realmente mejoran la performance del proceso que se deseaba atacar? Este problema es muy común y por suerte contamos con unas series de herramientas que nos permiten solucionarlo. Este pack se llama RML y consta de varias herramientas, comentaré las mas importantes a mi parecer:
ReadTrace, OStress, y ORCA.
El ReadTrace nos permite hacer un análisis te las trazas de SQL y pasarlas a lo que se llaman archivos RML. Estos archivos a diferencia de la traza, se encuentran organizados por proceso, de esta forma se hace posible el simulacro.
El OStress toma los archivos RML y los lanza al SQL simulando el comportamiento original, pudiendo configurarle si lo debe lanzar una sola vez o muchas, cada que intervalo, y muchas cosas mas.

En esté primer artículo les dejo un overview de las pruebas realizadas.
Las herramientas son muy configurables y de un uso muy simple e intuitivo, y al ser por linea de comando es facil de documentar los pasos a seguir.
El readtrace viene con 3 archivos de ejemplo para una carga full, media o liviana. La herramienta esta pensada para hacer reportes y para trabajar con el Ostress, pero la verdad es que ninguno de estos tres archivos de configuración es adecuado para el OS. El archivo full consume muchisimo espacio, y el mediano no posee varios eventos que son requeridos por el OS.
El Ostress es un poco caprichoso con los eventos que requiere y las columnas que se deben haber capturado. Algunas (a mi ver) están de mas pero bue... sus mensajes no son del todo intuitivos visto que algunas veces les va a decir "falta tal evento y tal otro" y en realidad falta uno solo de los dos, pero se debe a que usa el mismo mensaje para determinado grupo de eventos (ejemplo los started y completed, si falta uno de los dos le va a decir que faltan los dos).
Me armé una traza personalizada que tiene menos eventos que la full pero los suficientes para que funcione el OStress, intentando minimizar las columnas tambien (hay algunos warnings que me da pero alcanza para que ejecute). Probé en un sistema con carga moderada y tardó un varias horas en llegar al giga de traza, pero en sistemas con mucho movimiento en menos de una hora ya rozaba el giga... Es una herramienta que hay que tener cuidado como se la usa visto la gran demanda de recursos (principalmente disco, compare los contadores y no hubo diferencias significativas en la performance del sistema al realizar la traza) así que me parece que lo adecuado es definir una franja horaria crítica o modelo y utilizarla sobre dicha franja. Claramente no es una traza para dejar corriendo durante toda la jornada.

Pro: Es muy facil una vez capturado hacer un simulacro para poder comparar la performance de un sistema antes y despues de hacer un cambio

Contra: Pese a poder configurar el paralelismo y los delay entre conexion, se dificulta hacer simulaciones de una ventana de tiempo prolongada o hacer la captura sin saber cuando será el momento crítico.

Les dejo el link para bajarlo: http://support.microsoft.com/kb/944837

martes, 5 de octubre de 2010

Socorro! no puedo frenar del CDC!!!

El CDC de SQL 2008 es en muchos aspectos una bendición, pero en algunos casos se puede volver una pesadilla si lo deseamos apagar. Una catarata de errores nos pueden ocurrir al intentar desactivar el CDC de una base o de una tabla, principalmente si no dejamos todo intacto como cuando lo creamos (ejemplo renombramos la tabla que tenia CDC).
En este caso vamos a ver que si borramos la tabla pasamos a estar peor, porque ahora no solo que no nos deja desactivar el CDC de la base sino que aparecerán algunos errores, así que lo que debemos hacer es erradicar todo rastro.
Primero debemos eliminar "dbo.systranschemas" y luego todas las referencias en el schema CDC.
Con una consulta nos alcanza para encontrarlas:

select * from sys.objects where schema_id=schema_id('cdc')

Esto nos devolvera cuales son las tablas, procedures y functions que se crearon. Debemos eliminar todos estos, con especial cuidado en las funciones que algunas tienen nombres raros como "fn_cdc_get_net_changes_ ..." y si no la ponen entre corchetes no se podrán eliminar.
Una vez borradas todas estas tablas ejecutamos el comando:

EXECUTE sys.sp_cdc_disable_db

y dejamos que el SQL se encargue del resto. Les recuerdo que esto es un último recurso, no deben hacerlo salvo que sea extremadamente necesario, nunca es recomentable borrar la información propia del SQL.
Saludos!
Andrés

jueves, 16 de septiembre de 2010

Role de ejecución para todos los stored procedures

Una situación bastante común es querer que un usuario (o grupo de usuarios) tenga permiso de ejecución para todos los stored procedures de una base. El SQL Server nos brinda los roles db_datareader, db_datawriter u otros, pero ninguno da solamente esos permisos. Para poder hacer esto con roles es necesario asignarlo al role db_owner, lo cual da muchos mas permisos de los deseados.
La única forma es dar los permisos de cada stored de forma individual, lo cual si tenemos muchos puede ser realmente molesto.
La solución que planteo es hacer un grupo al cual llamé db_executeall. Armé un script que recorre todas las bases y en todas crea dicho role.
El segundo paso es crear un cursor que recorra todos los stored procedures y asigne los permisos al role. El código estaría estructurado de la siguiente forma:

Cursor que recorre bases
begin
Crear role db_executeall si no existe
Cursor que recorre procedures
begin
Asignar permiso de ejecución al role
end
end

Esto nos dejaría en cada base un role nuevo que tiene todos los permisos de ejecución así solamente debemos asignarlo ahí al usuario. Una idea extra es agregar este script en un job diario, o por hora, para asegurarnos que nuestro role siempre esté actualizado con todos los permisos.
Si alguién necesita el código puede escribirme.
Saludos!

martes, 7 de septiembre de 2010

Defaul role como en Oracle

Una cosa que mucha gente que trabaja con SQL Server extraña de Oracle es lo conocidos como Default Roles.
¿En que consiste esto? Consiste en tener un conjunto de permisos al comenzar la sesion y en tiempo de ejecución poder acceder a otros permisos de forma explicita. Un ejemplo util sería darle a un usuario permisos de DATAREADER con la opción de ascender a DATAWRITER. De esta forma si la mayoria de sus tareas consisten en solo leer datos no podría modificar cosas por error salvo que explicitamente suba sus permisos.
Para implementar esto haremos lo siguiente. Para empezar crearemos en la master (o en alguna base de DBA) una tabla que tenga la siguiente estructura:

T_Permisos(Usuario SYSNAME, Role SYSNAME, Defualt TINYINT)

Con esta tabla indicaremos los roles que tiene permitidos un usuario. La primer columna sera el nombre del LOGIN, la segunda el role y la última si este role lo tiene desde el comienzo o lo debe explicitar.
El segundo paso es generar un logon trigger. Este trigger lo que hará será leer la tabla con un filtro en el where de Default=1 y Usuario=ORIGINAL_LOGIN() y hacer con SQL Dinamico una asignación de permisos a los roles indicados y quitandole cualquier otro que pudiera tener.
Con esto ya tenemos garantizado que al momento de conectarse tendrá un subconjunto de permisos.
El último paso es permitir la escalada de permisos, lo cual lo haremos mediante un stored procedure. El stored GrantRoles(Role SYSNAME) deberán tener permiso de ejecución todos los miembros de public. Este stored lo que hará es un select para controlar si el usuario logueado tiene en la tabla registrado el rol que solicita, en caso negativo no se hace nada y en caso afirmativo se asigna de forma dinámica el role. Es importante que este stored utilice la sentencia EXECUTE AS SELF por ejemplo y se haya creado con un usuario administrador. La idea es que el no tenga los permisos pero el stored si, y con la lógica adecuada se los asigne.
Una vez terminada su tarea, cuando se desconecte mantendrá los permisos, pero al establecer una nueva conexion los perderá.
Es interesante el tema de conexiones simultaneas, ahí se pueden analizar varias variantes como controlar en la sys.dm_exec_sessions para ver de solo sacar los permisos si no hay otra conexion viva del mismo usuario.
Saludos!!

jueves, 26 de agosto de 2010

Restore

Este artículo es corto pero muchas veces me lo preguntaron y yo mismo lo he necesitado asi que me parece un buen dato para tener siempre a mano. Cuantas veces uno tiene que hacer un restore de una base de varios gigas y no tiene idea de cuanto va a tardar, y del otro lado del teléfono tenemos al usuario reclamando que quiere su base online hace una hora (aunque el pedido se haya realizado hace 15 minutos, siempre es así el usuario). Para esto podemos utilizar una vista del sistema, DM_EXEC_REQUESTS (http://msdn.microsoft.com/es-es/library/ms177648.aspx). Una consulta sencilla que me permitirá saber el tiempo que resta para un restore es la siguiente:

SELECT CONVERT(NVARCHAR(3), CAST(percent_complete AS INTEGER)) + '%' Porcentaje
, r.estimated_completion_time/(1000*60) MinutosRestantes
FROM SYS.DM_EXEC_REQUESTS r
WHERE percent_complete > 0

Esta consulta nso dirá el tiempo esperado para finalizar el restore pero tiene un problema, en caso de estar realizando varios restore al mismo tiempo no nos indica que a que base corresponde cada tiempo estimado. Esta información la podemos enriquecer haciendo un join con la información obtenida en el SP_WHO2 y así tener una consulta mucho mas completa que nos muestre también cual es la base sobre la cual operamos.

CREATE TABLE #sp_who2
(SPID INT,
Status VARCHAR(1000) NULL,
Login SYSNAME NULL,
HostName SYSNAME NULL,
BlkBy SYSNAME NULL,
DBName SYSNAME NULL,
Command VARCHAR(1000) NULL,
CPUTime INT NULL,
DiskIO INT NULL,
LastBatch VARCHAR(1000) NULL,
ProgramName VARCHAR(1000) NULL,
SPID2 INT,
REQUESTID INT)

INSERT INTO #sp_who2
EXEC sp_who2

SELECT w.DBName
, CONVERT(NVARCHAR(3), CAST(percent_complete AS INTEGER)) + '%' Porcentaje
, r.estimated_completion_time/(1000*60) MinutosRestantes
FROM #sp_who2 w, SYS.DM_EXEC_REQUESTS r
WHERE r.SESSION_ID = w.SPID
AND w.Command IN ('RESTORE DATABASE')
AND r.percent_complete>0
AND w.DBName<>'master'
ORDER BY 1

DROP TABLE #sp_who2

Saludos!

viernes, 13 de agosto de 2010

Todo sobre logins (parte 2)

Ya hablamos de como mantener bien nuestros logins, ahora lo que falta es controlar que todo funcione como queremos. La auditoria es una de las partes mas importantes a la hora de administrar la seguridad.
Hay varias cosas que podemos auditar pero principalmente quisiera hacer foco en dos:
1) Auditoria SQL
2) Logon triggers

En la auditoria podemos definir los eventos que queremos que se controlen. Esto en la versión 2008 se ha echo muy sencillo, con solo un par de clicks podemos hacerlo. Si en cambio nuestra base es SQL 2005 deberemos trabajar un poco mas y habilitar el service broker (ENABLE_BROKER) y tenemos varios eventos que esta bueno monitorear y loguear en una tabla:
AUDIT_LOGIN_CHANGE_PROPERTY_EVENT
AUDIT_LOGIN_FAILED
AUDIT_ADD_LOGIN_TO_SERVER_ROLE_EVENT
AUDIT_ADDLOGIN_EVENT

Estos eventos nos permitiran monitorear si se han creado nuevos logins o se le asignaron permisos a los ya existentes. Seguro se estarán preguntando "y no voy a auditar si se borraron loggins?" el evento cuando se borra un login es "AUDIT_ADDLOGIN_EVENT" pero en el xml del evento el subclass es 2 en lugar de 1.

Lo otro que podemos hacer interesante es definir logon tringgers y aquí podemos dejar volar nuestra imaginación. Entre las ideas que se pueden implementar estaría:
- Definir que un login pueda conectarse solamente desde un host en particular
- Definir que un login pueda tener N ocurrencias en el día o simultaneas
- Guardar en una tabla los horarios de login del usuario
- No permitir que dos usuarios se conecten de forma simultanea

y esas son solo algunas ideas, cada uno puede adaptarlas según las necesidades de su empresa