viernes, 26 de junio de 2015

Mantenimiento de índices- Identificar el porcentaje de fragmentación de los índices

Mantenimiento de índices – Identificar Fragmentación

Los índices como ya sabemos son de vital importancia para el motor de base de datos tanto para hacer consultas como procesos propios de manipulación de datos, igualmente la fragmentación de estos índices es una condición que se debe monitorear y solucionar como parte de la administración de las bases de datos.

La primera parte del mantenimiento debe ser monitorear la fragmentación de los índices para ellos es importante tener en cuenta los niveles de fragmentación que se pueden presentar:

A- Fragmentación Inocua: Se denomina a aquella fragmentación por la cual no deberíamos preocuparnos, estos casos son cuando el índice contiene menos de 1000 páginas o cuando la tabla es muy pequeña.

B- Fragmentación Baja: Corresponde a la fragmentación que no es superior al % 5 del índice. Para estos casos se recomienda no tomar acciones ya que puede ser mucho más el costo que el beneficio obtenido.

C- Fragmentación Media: es la fragmentación > % 5 y < = al %30. Se recomienda hacer un Reorganize de los índices.

D- Fragmentación Alta: es la fragmentación > al % 30. Se recomienda hacer un Rebuild de los índices.

Identificar el porcentaje de fragmentación de los índices

Para identificar la fragmentación de los índices el sistema ya cuenta con una vista prediseñada para esto sys.dm_db_index_physical_stats. Esta vista devuelve la información de tamaño y fragmentación de los datos y los índices de una tabla o vista especificada.

Para utilizar esta vista se deben proveer una serie de parámetros que corresponden a:

-          database_id: Identificador de la base de datos (NULL, 0 ó DEFAULT)

-          object_id: Es el identificador de objeto de la tabla o vista donde está activado el índice (NULL, 0 ó DEFAULT)

-          index_id: Es el identificador del índice. (NULL, 0, -1 ó DEFAULT)

-          partition_number: Es el número de partición en el objeto. (NULL, 0 ó DEFAULT)

-          Mode: Es el nombre del modo. (DEFAULT, NULL, LIMITED, SAMPLED o DETAILED)

Los valores entre paréntesis son los que se pueden usar por defecto

También utilizaremos sys.indexes que es una vista que contiene la información de los índices y las tablas o vistas que los contienen y la vista sys.objects que contiene la información de los objetos de la base de datos.

El script es el siguiente:

select CURRENT_TIMESTAMP as Fecha, DB_NAME(db_id()) as DatabaseName, @@servername Servidor, b.name as IndexName, obj.name as ObjectName,
a.avg_fragmentation_in_percent as '%Frag',  a.page_count as NumeroPaginas , a.fragment_count as PromPagFrag,a.index_type_desc as TipoIndice,
a.avg_fragment_size_in_pages, a.partition_number
FROM  sys.dm_db_index_physical_stats(db_id(), NULL, NULL, NULL, 'limited') as a
INNER JOIN sys.indexes as b
ON a.object_id = b.object_id AND a.index_id = b.index_id
INNER JOIN sys.objects as Obj
ON  a.object_id = Obj.object_id

El resultado de este script es algo así:


Como podemos ver el script nos dirá el porcentaje de fragmentación de cada índice de esta base, mostrándonos su nombre y en que tabla o vista esta creado, pero para poder capturar toda la información de todas las bases de datos de una instancia tendríamos que ejecutar el script en cada base de datos lo cual puede ser un poco tedioso por tal motivo sería mucho más sencillo implementar el procedimiento almacenado SP_MSFOREACHDB.

SP_MSFOREACHDB es un procedimiento almacenado de SQL Server que nos permite iterar con cada base de datos de una instancia para este caso en especial es muy oportuno, y para poder ver la información toda junta la introduciremos en una tabla temporal.

Primero crearemos la tabla temporal:

CREATE TABLE #INDEX(
       [fecha] [datetime] NULL,
       [dbname] [varchar](200) NULL,
       [SRV] [varchar](200) NULL,
       [INDICE] [varchar](500) NULL,
       [TABLA] [varchar](200) NULL,
       [FRAGM] [decimal](20, 5) NULL,
       [numeropaginas] [decimal](20, 5) NULL,
       [PROMPAGFRAG] [decimal](20, 4) NULL,
       [tipoindice] [varchar](200) NULL,
       [fragsizepages] [decimal](20, 5) NULL,
       [PARTS] [int] NULL
)
Los tipos de datos deben coincidir con los que arroja la consulta que planteamos antes
Posterior a crear la tabla temporal declararemos una variable que aloje el script que queremos ejecutar en todas las bases de datos
DECLARE @QUERY NVARCHAR(MAX)

Ahora le asignaremos a la variable el script que va a ejecutarse pero antes añadimos al script la sentencia ‘use ?’  teniendo en cuenta que ‘?’ funciona como un marcador para el nombre de la base de datos.

SET @QUERY = 'USE ? ;
select CURRENT_TIMESTAMP as hora, DB_NAME(db_id()) as DatabaseName, @@servername servidor, b.name as IndexName, obj.name as ObjectName,
a.avg_fragmentation_in_percent,  a.page_count, a.fragment_count,a.index_type_desc, a.avg_fragment_size_in_pages, a.partition_number
FROM  sys.dm_db_index_physical_stats(db_id(), NULL, NULL, NULL, "limited") AS a
INNER JOIN sys.indexes AS b
ON a.object_id = b.object_id AND a.index_id = b.index_id
INNER JOIN sys.objects as Obj
on  a.object_id = Obj.object_id'

Finalmente y una vez asignado el script utilizaremos el SP_MSFOREACHDB para que se ejecute en cada base de datos e inserte la información en la tabla temporal

INSERT INTO #INDEX
EXEC SP_MSFOREACHDB @QUERY

La ejecución nos dará como resultado algo así



Y si hacemos un select a la tabla temporal veremos



Para que sea más evidente haremos un select count distinct que nos permita ver cuantas bases de datos hay en la tabla



De esta manera y con este script podríamos hacer un monitoreo a la fragmentación de nuestros índices creando un Job que ejecute este script cada semana.


miércoles, 10 de junio de 2015

Creando una Traza SQL Server (Ejemplo)

Consulta y Creación de una Traza

Finalmente aplicaremos todas las sentencias para crear una traza que registre cada vez que:

° se inicia una instrucción Transact-SQL (event_id 40)

° se completa una instrucción Transact-SQL (event_id 41)

° se inicia un procedimiento almacenado (event_id 42)

° se completa un procedimiento almacenado (event_id 43)

° se inicia una instrucción Transact-SQL de un procedimiento almacenado (event_id 44)

° se ha creado un objeto, como para las instrucciones CREATE INDEX, CREATE TABLE o CREATE DATABASE (event_id 45)

° Realiza un seguimiento de las instrucciones Transact-SQL BEGIN, COMMIT, SAVE y ROLLBACK TRANSACTION (event_id 50)

Como primer paso consultaremos las trazas que estén creadas actualmente con el script

select * from sys.traces

go




Podemos notar que solo existe una traza que actualmente está en estado 1 es decir corriendo. Ahora crearemos una nueva traza lo primero seria definir variables con los argumentos necesarios para el procedimiento sp_trace_create.

declare @tracefile nvarchar(500) set @tracefile=N'F:\Trace\mi_primera_traza’ --(nombre y ubicación del archivo traza)

declare @trace_id int --(Declaramos el id de la traza y lo dejamos sin valor para que el procedimiento asigne este valor)

declare @maxsize bigint --(Declaramos la variable para el tamaño máximo que queremos que tenga cada archivo de la traza)

set @maxsize =20 --(asignamos el valor máximo de tamaño en este caso será de 20MB)

Una vez declarados todos los argumentos procedemos a crear la traza

exec sp_trace_create @trace_id output,2,@tracefile ,@maxsize

go

Si notamos el segundo argumento del procedimiento es un ‘2’ lo cual equivale a que será una traza de opción TRACE_FILE_ROLLOVER.



Si nuevamente consultamos las trazas existentes debe aparecer la que acabamos de crear




Configuración de una Traza

El procedimiento sp_trace_create solo crea la traza pero ahora es necesario añadir los eventos y columnas que la traza va a monitorear para esto usamos sp_trace_setevent, pero el proceso puede llegar a ser engorroso ya que por cada evento que vamos a añadir debemos ejecutar una vez el procedimiento y por cada columna para ese evento debemos ejecutar el procedimiento por ejemplo.

exec sp_trace_setevent 2,50, 1,1

Aquí añadimos a la traza 2 el evento 50 la columna 1 en estado on, para añadir más columnas al mismo debería hacer.

exec sp_trace_setevent 2,50, 2,1;
exec sp_trace_setevent 2,50, 3,1;
exec sp_trace_setevent 2,50, 4,1;

y así consecutivamente hasta poner todas las columnas que quiero para ese evento y posteriormente repetir el proceso para otros eventos, pero para hacer esto de una manera mas sencilla implementaremos un ciclo while de la siguiente manera.

declare @trace_id int --(Declaramos una variable para guardar el id de la traza)

set @trace_id=2 --(Asignamos el id de la traza a la variable)

declare @on bit --(Declaramos una variable para el on/off de la traza)

set @on=1 --(Asignamos el valor a la variable anterior)

declare @current_num int --(Declaramos una variable para el numero de la columna)

set @current_num =1 --(Asignamos un valor inicial)

while(@current_num <65) --(Iniciamos el while y le decimos que realice las acciones hasta que la variable de las columnas llegue a 65)
      begin

/*Iniciamos el proceso del ciclo en el que empezaremos a añadir los eventos y sus columnas, como esto se repetirá hasta que la variable de las columnas llegue a 65, quiere decir que nuestra traza tendrá las columnas de la 1 a la 65 para los eventos que mencionamos antes.*/

exec sp_trace_setevent @trace_id,50, @current_num,@on
exec sp_trace_setevent @trace_id,40, @current_num,@on
exec sp_trace_setevent @trace_id,41, @current_num,@on
exec sp_trace_setevent @trace_id,42, @current_num,@on
exec sp_trace_setevent @trace_id,43, @current_num,@on
exec sp_trace_setevent @trace_id,44, @current_num,@on
exec sp_trace_setevent @trace_id,45, @current_num,@on
set @current_num=@current_num+1
end
go



En este momento nuestra traza ya tiene los eventos y columnas a monitorear


Crear filtros y cambios de estados una Traza

Pero para hacer aún más específico este monitoreo utilizaremos un filtro con el procedimiento sp_trace_setfilter.

sp_trace_setfilter  2, 11, 0, 6 , N'%IUSR_Satelite%';
 go

En este filtro lo que estamos especificando es que filtre para la traza con id 2 en la columna 11 “LoginName” (Nombre de inicio de sesión de SQL Server del cliente.) y cuando ese nombre de inicio de sesión contenga IUSR_Satelite.

En query correspondería a decir Select * from traza2 where LoginName like’%IUSR_Satelite%’



Nuestra traza ya está lista para iniciarse para esto debemos utilizar el procedimiento sp_trace_setstatus porque en este momento la traza está en estado detenido y debemos iniciarla.

declare @trace_id int
set @trace_id=2
exec sp_trace_setstatus  @trace_id,1

Con esto nuestra traza se inicia como no establecimos una hora para que se detenga la detención debe hacerse manual.



Si nuevamente consultamos las trazas existentes veremos que el estado de nuestra traza paso de 0 a 1 es decir iniciada.



Para detenerla nuevamente solo ejecutamos el script anterior pero cambiamos el 1 por 0



Y para eliminar la traza utilizamos el estado 2


Posterior a esto consultamos las trazas y ya no se nos debe mostrar.



Los datos de la traza quedan almacenados en archivos en la ubicación que determinamos al momento de crear la traza.



Y estos pueden ser consultados con el siguiente script

select LoginName,DatabaseName,* from ::fn_trace_gettable(N'F:\Trace\mi_primera_traza.trc',default)
where TextData like '%DateOnly%'
go



Las utilidades dadas a las trazas dentro de la administración son múltiples y dependen de la configuración que se aplique a la misma.


Creación de una Traza en SQL Server

Traza SQL

SQL nos provee procedimientos almacenados para la creación de trazas que nos permiten configurarlas manualmente para de esta manera suplir el uso del SQL Server Profiler y hacer un seguimiento específico a procedimientos y/o procesos puntuales.

Procedimientos de una traza y descripción

Como se mencionó antes existen una serie de procedimientos almacenados que nos permiten la creación de una traza manual, a continuación listaremos esos procedimientos y definiremos su funcionalidad:

·         fn_trace_geteventinfo
Devuelve información acerca de los eventos incluidos en la traza.

Como resultado obtendremos una tabla con la siguiente estructura.

Nombre de columna
Tipo de datos
Descripción
eventid
int
Id. del evento del que se hace el seguimiento.
columnid
int
Números de Id. de todas las columnas recopiladas para cada evento.




·         fn_trace_getinfo

Devuelve información acerca de una traza especificada o de todas las trazas existentes.
Como resultado obtendremos una tabla con la siguiente estructura.



Nombre de columna
Tipo de datos
Descripción
traceid
int
Id. de seguimiento.
property
int
Propiedad del seguimiento:
1= Opciones de seguimiento
2 = Nombre de archivo
3 = Tamaño máximo
4 = Hora de detención
5 = Estado actual del seguimiento. 0 = detenido. 1 = en ejecución.
value
sql_variant
Información acerca de la propiedad de seguimiento especificado.




·         sp_trace_create

Crea una definición de traza. El nuevo seguimiento estará en estado de detención.

Para la creación de una traza debemos tener en cuenta los siguientes argumentos:

-       id_traza: Es el número que se le asigna a la nueva Traza. Sera el número con el cual identificaremos la traza para procesos de identificación, modificación y control.(Este valor es asignado por SQL pero debe ser declarado por el usuario como una salida)

-       option_value: Es un valor entero. A través de este atributo especificaremos la opción de traza que implementaremos que puede ser:

Nombre de la opción
Valor de la opción
Descripción
TRACE_FILE_ROLLOVER
2
Especifica que al alcanzar el valor establecido en max_file_size, el archivo de traza actual se cerrará y se creará un archivo. Todos los nuevos registros se escribirán en el archivo nuevo. El archivo nuevo tendrá el mismo nombre que el archivo anterior, pero se agregará un entero para indicar su secuencia. Por ejemplo, si el archivo de traza original se llama FILENAME.TRC, siguiente archivo de traza se llamará FILENAME_1.TRC, el siguiente archivo de traza se llamará FILENAME_2.TRC, y así sucesivamente.
A medida que se creen más archivos de traza de sustitución incremental, el valor entero agregado al nombre del archivo aumentará secuencialmente.
SQL Server utiliza el valor predeterminado de max_file_size (5 MB) si se especifica esta opción sin especificar un valor para max_file_size.
SHUTDOWN_ON_ERROR
4
Especifica que, si por cualquier razón, no se puede escribir la traza en el archivo, SQL Server se cerrará. Esta opción es muy útil cuando se realizan trazas de auditoría de seguridad.
TRACE_PRODUCE_BLACKBOX
8
Especifica que el servidor guardará un registro de los últimos 5 MB de información de traza producidos por el servidor. TRACE_PRODUCE_BLACKBOX es incompatible con las demás opciones.

-       trace_file: En este argumento especificamos el nombre y la ubicación en la que se guardara la traza  y el archivo debe guardarse con la extensión .trc

-       max_file_size: Especifica el tamaño máximo en megabytes (MB) que puede alcanzar un archivo de traza.

-       stop_time: Especifica la fecha y la hora de la detención de la traza. Si el valor se deja en NULL, la traza se ejecuta hasta que se detiene de forma manual.

-       max_rollover_files: Especifica el número máximo de archivos de traza que pueden existir con el mismo nombre y solo es aplicable si la opción de traza seleccionada es TRACE_FILE_ROLLOVER según el número máximo de archivos que se determine al llegar al límite se borrara el archivo más antiguo con el mismo nombre y se escribirá uno nuevo.

Los tres atributos anteriores están muy relacionados con las option values que se hayan determinado para la traza, por ejemplo si se determinó que la traza será TRACE_FILE_ROLLOVER y un max_file_size de 10 (MB) sin stop_time el archivo .trc llegara a 10MB y creara uno nuevo con el mismo nombre mas el consecutivo y continuara repitiendo la operación cada que el archivo .trc alcance los 10MB solo dejara de hacerlo cuando la traza se detenga manualmente o se apague el servidor.

Es por ese motivo que debe tenerse cuidado al crear la traza por que en una base de datos con alta transaccionaldad se podría llenar un disco de 10GB en una hora o menos.

El sp_trace_create es un procedimiento almacenado que remplaza la mayor parte de las acciones que se utilizaban en versiones anteriores de SQL Server con los procedimientos almacenados extendidos como:

o   xp_trace_addnewqueue
o   xp_trace_setqueuecreateinfo
o   xp_trace_setqueuedestination

sp_trace_setevent

Agrega o quita un evento o una columna de evento de una traza y solo puede ser ejecutado para trazas ya creadas y que estén detenidas.

            Los argumentos para el uso de esta función corresponden a:

-       trace_id: Corresponde al número de identificación de la traza.

-       event_id: Es el Id. del evento que se debe activar, los eventos que podemos adicionar a una traza son 235.

-       column_id: Es el Id. de la columna que va a agregarse para el evento las columnas que es posible agregar son 64.

La lista completa con sus descripciones tanto de los eventos como de las columnas  se puede encontrar en el siguiente link:

-       on: Este último argumento especifica la activación o desactivación del evento, (1) ON (0) OFF

sp_trace_setevent

Modifica el estado actual de la traza especificada.

Los argumentos para el uso de esta función:

-       trace_id: Corresponde al número de identificación de la traza.

-       Status: Por medio de este argumento podemos determinar en que estado estará la traza
(0)  Detenida
(1)  Inicia
(2)  Cierra y elimina

sp_trace_setevent
Aplica un filtro a una traza y solo puede ser ejecutado para trazas ya creadas y que estén detenidas.

Los argumentos para el uso de esta función:

-       trace_id: Corresponde al número de identificación de la traza.

-       column_id: Determina la columna a la que se aplicara el filtro.

-       logical_operator: Especifica el operador lógico (0) AND (1) OR

-       comparison_operator: Determina la comparación a hacer según la siguiente tabla:

o   (0) =(Es igual a)
o   (1) <>(Diferente a)
o   (2) > (Mayor que)
o   (3) < (Menor que)
o   (4) >= (Mayor o igual)
o   (5) <= (Menor o igual)
o   (6) LIKE
o   (7) NOT LIKE


-       Value: En este argumento ponemos el valor que se va a filtrar. (El tipo de dato a filtrar debe coincidir con el de la columna si es id. Objeto que es un tipo int el valor del value debe corresponder a un número)