Estoy trabajando en una solución de mantenimiento personalizada utilizando la sys.dm_db_index_physical_stats
vista. Actualmente tengo referencia de un procedimiento almacenado. Ahora, cuando ese procedimiento almacenado se ejecuta en una de mis bases de datos, hace lo que quiero que haga y despliega una lista de todos los registros relacionados con cualquier base de datos. Cuando lo coloco en una base de datos diferente, despliega una lista de todos los registros relacionados solo con esa base de datos.
Por ejemplo (código en la parte inferior):
- La consulta ejecutada en la Base de datos 6 muestra información [solicitada] para las bases de datos 1-10.
- La consulta ejecutada en la Base de datos 3 muestra información [solicitada] solo para la base de datos 3.
La razón por la que quiero este procedimiento específicamente en la base de datos tres es porque prefiero mantener todos los objetos de mantenimiento dentro de la misma base de datos. Me gustaría tener este trabajo en la base de datos de mantenimiento y trabajar como si estuviera en la base de datos de esa aplicación.
Código:
ALTER PROCEDURE [dbo].[GetFragStats]
@databaseName NVARCHAR(64) = NULL
,@tableName NVARCHAR(64) = NULL
,@indexID INT = NULL
,@partNumber INT = NULL
,@Mode NVARCHAR(64) = 'DETAILED'
AS
BEGIN
SET NOCOUNT ON;
DECLARE @databaseID INT, @tableID INT
IF @databaseName IS NOT NULL
AND @databaseName NOT IN ('tempdb','ReportServerTempDB')
BEGIN
SET @databaseID = DB_ID(@databaseName)
END
IF @tableName IS NOT NULL
BEGIN
SET @tableID = OBJECT_ID(@tableName)
END
SELECT D.name AS DatabaseName,
T.name AS TableName,
I.name AS IndexName,
S.index_id AS IndexID,
S.avg_fragmentation_in_percent AS PercentFragment,
S.fragment_count AS TotalFrags,
S.avg_fragment_size_in_pages AS PagesPerFrag,
S.page_count AS NumPages,
S.index_type_desc AS IndexType
FROM sys.dm_db_index_physical_stats(@databaseID, @tableID,
@indexID, @partNumber, @Mode) AS S
JOIN
sys.databases AS D ON S.database_id = D.database_id
JOIN
sys.tables AS T ON S.object_id = T.object_id
JOIN
sys.indexes AS I ON S.object_id = I.object_id
AND S.index_id = I.index_id
WHERE
S.avg_fragmentation_in_percent > 10
ORDER BY
DatabaseName, TableName, IndexName, PercentFragment DESC
END
GO
fuente
Respuestas:
Una forma sería hacer un procedimiento del sistema
master
y luego crear un contenedor en su base de datos de mantenimiento. Tenga en cuenta que esto solo funcionará para una base de datos a la vez.Primero, en master:
Ahora, en su base de datos de mantenimiento, cree un contenedor que use SQL dinámico para establecer el contexto correctamente:
(La razón por la que el nombre de la base de datos no puede ser realmente
NULL
es porque no puede unirse a cosas comosys.objects
ysys.indexes
dado que existen independientemente en cada base de datos. Por lo tanto, tal vez tenga un procedimiento diferente si desea información de toda la instancia).Ahora puede llamar a esto para cualquier otra base de datos, p. Ej.
Y siempre puede crear un
synonym
en cada base de datos para que ni siquiera tenga que hacer referencia al nombre de la base de datos de mantenimiento:Otra forma sería usar SQL dinámico, sin embargo, esto también funcionará solo para una base de datos a la vez:
Otra forma sería crear una vista (o función con valores de tabla) para unir los nombres de tabla e índice de todas sus bases de datos, sin embargo, tendría que codificar los nombres de las bases de datos en la vista y mantenerlos a medida que agrega / eliminar las bases de datos que desea permitir que se incluyan en esta consulta. Esto, a diferencia de los demás, le permitirá recuperar estadísticas de varias bases de datos a la vez.
Primero, la vista:
Entonces el procedimiento:
fuente
Bueno, hay malas noticias, buenas noticias con una trampa, y algunas muy buenas noticias.
Las malas noticias
Los objetos T-SQL se ejecutan en la base de datos donde residen. Hay dos excepciones (no muy útiles):
sp_
y que existen en la[master]
base de datos (no es una gran opción: una base de datos a la vez, agregando algo[master]
, posiblemente agregando sinónimos a cada base de datos, lo que debe hacerse para cada nueva base de datos)sp_
almacenado)[master]
.Las buenas noticias (con una trampa)
Muchas personas (¿quizás la mayoría?) Conocen las funciones integradas para obtener algunos metadatos realmente comunes:
El uso de estas funciones puede eliminar la necesidad de unir
sys.databases
(aunque este no es realmente un problema),sys.objects
(preferido sobre elsys.tables
que excluye las vistas indexadas) ysys.schemas
(se perdió esa, y no todo está en eldbo
esquema ;-). Pero incluso con la eliminación de tres de las cuatro uniones, seguimos siendo funcionalmente el mismo lugar, ¿verdad? Wrong-o!Una de las buenas características de las funciones
OBJECT_NAME()
yOBJECT_SCHEMA_NAME()
es que tienen un segundo parámetro opcional para@database_id
. Es decir, mientras UNIRSE a esas tablas (excepto parasys.databases
) es específico de la base de datos, el uso de estas funciones le brinda información de todo el servidor. Incluso OBJECT_ID () permite la información de todo el servidor al darle un nombre de objeto totalmente calificado.Al incorporar estas funciones de metadatos en la consulta principal, podemos simplificar y al mismo tiempo expandirnos más allá de la base de datos actual. El primer paso de refactorizar la consulta nos da:
Y ahora para la "captura": no hay una función de metadatos para obtener nombres de índice, y mucho menos uno para todo el servidor. ¿Entonces es eso? ¿Estamos al 90% completos y todavía estamos atrapados necesitando estar en una base de datos particular para obtener
sys.indexes
datos? ¿Realmente necesitamos crear un procedimiento almacenado para usar Dynamic SQL para completar, cada vez que se ejecuta nuestro proceso principal, una tabla temporal de todas lassys.indexes
entradas en todas las bases de datos para que podamos UNIRSE a ella? ¡NO!La muy buena noticia
Entonces, viene una pequeña característica que a algunas personas les encanta odiar, pero cuando se usa correctamente, puede hacer cosas increíbles. Sí: SQLCLR. ¿Por qué? Debido a que las funciones SQLCLR obviamente pueden enviar sentencias SQL, pero por la misma naturaleza de enviar desde el código de la aplicación, es SQL dinámico. Entonces, a diferencia de las funciones T-SQL, las funciones SQLCLR pueden inyectar un nombre de base de datos en la consulta antes de ejecutarla. Es decir, podemos crear nuestra propia función de reflejar la capacidad de
OBJECT_NAME()
yOBJECT_SCHEMA_NAME()
para tomar unadatabase_id
y obtener la información de la base de datos.El siguiente código es esa función. Pero toma un nombre de base de datos en lugar de una ID para que no tenga que hacer el paso adicional de buscarlo (lo que lo hace un poco menos complicado y un poco más rápido).
Si se da cuenta, estamos utilizando la conexión de contexto, que no solo es rápida, sino que también funciona en
SAFE
ensamblajes. Sí, esto funciona en una Asamblea marcada comoSAFE
, por lo que (o sus variaciones) incluso debería funcionar en Azure SQL Database V12(el soporte para SQLCLR se eliminó, de manera bastante abrupta, de Azure SQL Database en abril de 2016) .Entonces, nuestra refactorización de segundo paso de la consulta principal nos da lo siguiente:
¡Eso es! Tanto este SQLCLR Scalar UDF como su procedimiento almacenado T-SQL de mantenimiento pueden vivir en la misma
[maintenance]
base de datos centralizada . Y, no tiene que procesar una base de datos a la vez; ahora tiene funciones de metadatos para toda la información dependiente que abarca todo el servidor.PS No hay
.IsNull
comprobación de los parámetros de entrada en el código C # ya que el objeto contenedor T-SQL debe crearse con laWITH RETURNS NULL ON NULL INPUT
opción:Notas adicionales:
El método descrito aquí también se puede usar para resolver otros problemas muy similares de la falta de funciones de metadatos entre bases de datos. La siguiente sugerencia de Microsoft Connect es un ejemplo de uno de esos casos. Y, al ver que Microsoft lo ha cerrado como "No solucionará", está claro que no están interesados en proporcionar funciones integradas como
OBJECT_NAME()
para satisfacer esta necesidad (de ahí la solución que se publica en esa sugerencia :-).Agregue la función de metadatos para obtener el nombre del objeto de hobt_id
Para obtener más información sobre el uso de SQLCLR, eche un vistazo a la serie Stairway to SQLCLR que estoy escribiendo en SQL Server Central (se requiere registro gratuito; lo siento, no controlo las políticas de ese sitio).
La
IndexName()
función SQLCLR que se muestra arriba está disponible, precompilada, en un script fácil de instalar en Pastebin. La secuencia de comandos habilita la función "Integración CLR" si aún no está habilitada y el conjunto está marcado comoSAFE
. Se compila contra .NET Framework versión 2.0 para que funcione en SQL Server 2005 y versiones posteriores (es decir, todas las versiones que admiten SQLCLR).Función de metadatos SQLCLR para la base de datos cruzada IndexName ()
Si alguien está interesado en la
IndexName()
función SQLCLR y en más de 320 funciones y procedimientos almacenados, está disponible en la biblioteca SQL # (de la que soy autor). Tenga en cuenta que, si bien hay una versión gratuita, la función Sys_IndexName solo está disponible en la versión completa (junto con una función similar de Sys_AssemblyName ).fuente