Estou trabalhando em uma solução de manutenção personalizada usando a sys.dm_db_index_physical_stats
visualização. Atualmente, ele está sendo referenciado a partir de um procedimento armazenado. Agora, quando esse procedimento armazenado é executado em um dos meus bancos de dados, ele faz o que eu quero e faz uma lista de todos os registros relacionados a qualquer banco de dados. Quando o coloco em um banco de dados diferente, ele exibe uma lista de todos os registros relacionados apenas a esse banco de dados.
Por exemplo (código na parte inferior):
- A consulta executada no banco de dados 6 mostra informações [solicitadas] para os bancos de dados 1-10.
- A consulta executada no banco de dados 3 mostra informações [solicitadas] apenas para o banco de dados 3.
O motivo pelo qual desejo esse procedimento especificamente no banco de dados três é porque prefiro manter todos os objetos de manutenção no mesmo banco de dados. Eu gostaria que esse trabalho estivesse no banco de dados de manutenção e funcionasse como se estivesse no banco de dados do aplicativo.
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
fonte
Respostas:
Uma maneira seria executar um procedimento do sistema
master
e criar um wrapper no banco de dados de manutenção. Observe que isso funcionará apenas para um banco de dados por vez.Primeiro, no mestre:
Agora, no seu banco de dados de manutenção, crie um wrapper que use SQL dinâmico para definir o contexto corretamente:
(A razão pela qual o nome do banco de dados não pode realmente ser
NULL
é porque você não pode ingressar em coisas comosys.objects
e,sys.indexes
uma vez que elas existem independentemente em cada banco de dados. Portanto, talvez tenha um procedimento diferente se desejar informações em toda a instância.)Agora você pode chamar isso para qualquer outro banco de dados, por exemplo
E você sempre pode criar um
synonym
em cada banco de dados para não precisar nem fazer referência ao nome do banco de dados de manutenção:Outra maneira seria usar o SQL dinâmico, mas isso também funcionará apenas para um banco de dados por vez:
Outra maneira seria criar uma visualização (ou função com valor de tabela) para unir os nomes de tabelas e índices de todos os seus bancos de dados; no entanto, você teria que codificar os nomes dos bancos de dados na visualização e mantê-los à medida que você adiciona / remova os bancos de dados que você deseja permitir que sejam incluídos nesta consulta. Ao contrário dos outros, isso permitiria recuperar estatísticas para vários bancos de dados de uma só vez.
Primeiro, a visão:
Então o procedimento:
fonte
Bem, há más notícias, boas notícias e algumas realmente boas.
As más notícias
Os objetos T-SQL são executados no banco de dados em que residem. Existem duas exceções (não muito úteis):
sp_
e que existem no[master]
banco de dados (não é uma ótima opção: um banco de dados de cada vez, adicionando algo a[master]
, possivelmente adicionando sinônimos a cada banco de dados, o que deve ser feito para cada novo banco de dados)sp_
armazenado[master]
.As boas notícias (com uma pegadinha)
Muitas pessoas (talvez a maioria?) Estão cientes das funções internas para obter alguns metadados realmente comuns:
O uso dessas funções pode eliminar a necessidade de os JOINs
sys.databases
(embora este não seja realmente um problema),sys.objects
(preferencial sobre osys.tables
que exclui as exibições indexadas) esys.schemas
(você estava sentindo falta dessa, e nem tudo está nodbo
esquema ;-). Mas mesmo com a remoção de três dos quatro JOINs, ainda estamos funcionalmente no mesmo lugar, certo? Errado!Um dos bons recursos das funções
OBJECT_NAME()
eOBJECT_SCHEMA_NAME()
é que eles têm um segundo parâmetro opcional para@database_id
. O que significa que, enquanto JOIN nessas tabelas (excetosys.databases
) é específico do banco de dados, o uso dessas funções fornece informações para todo o servidor. Até o OBJECT_ID () permite informações em todo o servidor, fornecendo a ele um nome de objeto totalmente qualificado.Ao incorporar essas funções de metadados na consulta principal, podemos simplificar e, ao mesmo tempo, expandir além do banco de dados atual. A primeira passagem de refatoração da consulta nos fornece:
E agora para a "captura": não há função de meta-dados para obter nomes de índices, muito menos uma para todo o servidor. Então é isso? Estamos 90% completos e ainda assim precisamos estar em um banco de dados específico para obter
sys.indexes
dados? Realmente precisamos criar um procedimento armazenado para usar o SQL dinâmico para preencher, sempre que nosso proc principal for executado, uma tabela temporária de todas assys.indexes
entradas em todos os bancos de dados para que possamos ingressar nele? NÃO!As boas notícias
Então vem um pequeno recurso que algumas pessoas adoram odiar, mas quando usadas corretamente, podem fazer coisas incríveis. Sim: SQLCLR. Por quê? Como as funções SQLCLR podem obviamente enviar instruções SQL, mas pela própria natureza de enviar a partir do código do aplicativo, é Dynamic SQL. Portanto, diferentemente das funções T-SQL, as funções SQLCLR podem injetar um nome de banco de dados na consulta antes de executá-la. Ou seja, podemos criar nossa própria função para espelhar a capacidade
OBJECT_NAME()
eOBJECT_SCHEMA_NAME()
obter umdatabase_id
e obter as informações desse banco de dados.O código a seguir é essa função. Mas é preciso um nome de banco de dados em vez de ID, para que não seja necessário fazer a etapa extra de procurá-lo (o que o torna um pouco menos complicado e um pouco mais rápido).
Se você perceber, estamos usando a Conexão de Contexto, que não é apenas rápida, mas também funciona em
SAFE
Assemblies. Sim, isso funciona em uma montagem marcada comoSAFE
, portanto (ou variações dele) deve funcionar no Banco de Dados SQL do Azure V12(o suporte ao SQLCLR foi removido, de forma abrupta, do Banco de Dados SQL do Azure em abril de 2016) .Portanto, nossa refatoração de segunda passagem da consulta principal fornece o seguinte:
É isso aí! Esse UDF escalar do SQLCLR e o procedimento armazenado T-SQL de manutenção podem viver no mesmo
[maintenance]
banco de dados centralizado . E você não precisa processar um banco de dados por vez; agora você tem funções de metadados para todas as informações dependentes de todo o servidor.PS Não há
.IsNull
verificação dos parâmetros de entrada no código C #, pois o objeto wrapper T-SQL deve ser criado com aWITH RETURNS NULL ON NULL INPUT
opção:Notas Adicionais:
O método descrito aqui também pode ser usado para resolver outros problemas muito semelhantes de funções ausentes de meta-dados entre bancos de dados. A sugestão a seguir do Microsoft Connect é um exemplo de um desses casos. E, visto que a Microsoft a fechou como "Não Consertará", fica claro que eles não estão interessados em fornecer funções internas
OBJECT_NAME()
que atendam a essa necessidade (daí a Solução Alternativa publicada nessa Sugestão :-).Adicione a função de metadados para obter o nome do objeto de hobt_id
Para saber mais sobre o uso do SQLCLR, consulte a série Stairway to SQLCLR que estou escrevendo no SQL Server Central (registro gratuito é necessário; desculpe, não controlo as políticas desse site).
A
IndexName()
função SQLCLR mostrada acima está disponível, pré-compilada, em um script fácil de instalar no Pastebin. O script habilita o recurso "Integração do CLR", se ainda não estiver ativado, e o Assembly está marcado comoSAFE
. Ele é compilado no .NET Framework versão 2.0 para funcionar no SQL Server 2005 e em versões mais recentes (ou seja, todas as versões que oferecem suporte ao SQLCLR).Função de metadados SQLCLR para IndexName () de banco de dados
Se alguém estiver interessado na
IndexName()
função SQLCLR e mais de 320 outras funções e procedimentos armazenados, ele estará disponível na biblioteca SQL # (da qual sou o autor). Observe que, embora exista uma versão gratuita, a função Sys_IndexName só está disponível na versão completa (junto com uma função semelhante Sys_AssemblyName ).fonte