Veritabanı bağlamında çağrı yapmak için merkezi saklı yordam


17

sys.dm_db_index_physical_statsGörünümü kullanarak özelleştirilmiş bir bakım çözümü üzerinde çalışıyorum . Şu anda bir saklı yordamdan başvurulan var. Şimdi bu saklı yordam veritabanlarımdan birinde çalıştığında, ne yapmak istediğimi yapar ve herhangi bir veritabanı ile ilgili tüm kayıtların bir listesini aşağı çeker. Ben farklı bir veritabanına yerleştirdiğimde sadece o DB ile ilgili tüm kayıtların bir listesini aşağı çeker.

Örneğin (alttaki kod):

  • Veritabanı 6'ya karşı yapılan sorgu, 1-10 veritabanları için [istenen] bilgileri gösterir.
  • Veritabanı 3'e karşı yapılan sorgu, yalnızca veritabanı 3 için [istenen] bilgileri gösterir.

Bu yordamı özellikle üç veritabanı üzerinde istememin nedeni, tüm bakım nesnelerini aynı veritabanında tutmayı tercih etmemdir. Bu işin bakım veritabanında oturup bu uygulama veritabanındaymış gibi çalışmasını istiyorum.

Kod:

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

4
@JoachimIsaksson soru, yordamın her veritabanına bir kopyasını koymak yerine, bakım veritabanlarında yordamın tek bir kopyasının nasıl oluşturulacağı, DMV'ye diğer veritabanlarında nasıl başvurulacağı gibi görünüyor.
Aaron Bertrand

Üzgünüm daha net değildim, birkaç gündür buna bakıyordum. Aaron yerinde. Bu SP bakım veri tabanımda sunucu genelinde veri kapma yeteneği ile oturmak istiyorum. Durum olarak, bakım DB'mde oturduğunda, yalnızca bakım DB'sinin parçalanma verilerini çeker. Ne karıştırıyorum, neden bu aynı SP farklı bir veritabanına yerleştirmek ve aynı şekilde yürütmek, bu sunucu genelinde parçalanma verileri çekiyor mu? Bu SP'nin bakım veritabanından çalışabilmesi için değiştirilmesi gereken bir ayar veya ayrıcalık var mı?

(Mevcut yaklaşımınızın, iki farklı şema altında aynı ada sahip iki tablo olabileceğini göz ardı ettiğini unutmayın. Cevabımdaki önerilere ek olarak şema adını giriş ve / veya çıktının bir parçası olarak değerlendirmek isteyebilirsiniz.)
Aaron Bertrand

Yanıtlar:


15

Bunun bir yolu, master oluşturmak ve daha sonra bakım veritabanınızda bir sarıcı oluşturmak olacaktır. Bunun aynı anda yalnızca bir veritabanı için çalışacağını unutmayın.

İlk olarak, master'da:

USE [master];
GO
CREATE PROCEDURE dbo.sp_GetFragStats -- sp_prefix required
  @tableName    NVARCHAR(128) = NULL,
  @indexID      INT           = NULL,
  @partNumber   INT           = NULL,
  @Mode         NVARCHAR(20)  = N'DETAILED'
AS
BEGIN
  SET NOCOUNT ON;

  SELECT
    DatabaseName    = DB_NAME(),
    TableName       = t.name,
    IndexName       = i.name,
    IndexID         = s.index_id,
    PercentFragment = s.avg_fragmentation_in_percent,
    TotalFrags      = s.fragment_count,
    PagesPerFrag    = s.avg_fragment_size_in_pages,
    NumPages        = s.page_count,
    IndexType       = s.index_type_desc
    -- shouldn't s.partition_number be part of the output as well?
  FROM sys.tables AS t
  INNER JOIN sys.indexes AS i
    ON t.[object_id] = i.[object_id]
    AND i.index_id = COALESCE(@indexID, i.index_id)
    AND t.name = COALESCE(@tableName, t.name)
  CROSS APPLY
    sys.dm_db_index_physical_stats(DB_ID(), t.[object_id], 
      i.index_id, @partNumber, @Mode) AS s
  WHERE s.avg_fragmentation_in_percent > 10
  -- probably also want to filter on minimum page count too
  -- do you really care about a table that has 100 pages?
  ORDER BY 
    DatabaseName, TableName, IndexName, PercentFragment DESC;
END
GO
-- needs to be marked as a system object:
EXEC sp_MS_MarkSystemObject N'dbo.sp_GetFragStats';
GO

Şimdi, bakım veritabanınızda, içeriği doğru şekilde ayarlamak için dinamik SQL kullanan bir sarıcı oluşturun:

USE YourMaintenanceDatabase;
GO
CREATE PROCEDURE dbo.GetFragStats
  @DatabaseName SYSNAME,      -- can't really be NULL, right?
  @tableName    NVARCHAR(128) = NULL,
  @indexID      INT           = NULL,
  @partNumber   INT           = NULL,
  @Mode         NVARCHAR(20)  = N'DETAILED'
AS
BEGIN
  DECLARE @sql NVARCHAR(MAX);

  SET @sql = N'USE ' + QUOTENAME(@DatabaseName) + ';
    EXEC dbo.sp_GetFragStats @tableName, @indexID, @partNumber, @Mode;';

  EXEC sp_executesql 
    @sql,
    N'@tableName NVARCHAR(128),@indexID INT,@partNumber INT,@Mode NVARCHAR(20)',
    @tableName, @indexID, @partNumber, @Mode;
END
GO

(Veritabanı adının gerçekten olamamasının NULLnedeni , her veritabanında bağımsız olarak var olduğu sys.objectsve sys.indexesbu gibi şeylere katılamayacağınızdır . Bu nedenle, örnek genelinde bilgi istiyorsanız, belki de farklı bir yordamınız olabilir.)

Şimdi bunu başka herhangi bir veritabanı için arayabilirsiniz, ör.

EXEC YourMaintenanceDatabase.dbo.GetFragStats 
  @DatabaseName = N'AdventureWorks2012',
  @TableName    = N'SalesOrderHeader';

Ve synonymher veritabanında her zaman bir tane oluşturabilirsiniz, böylece bakım veritabanının adına başvurmanıza bile gerek kalmaz:

USE SomeOtherDatabase;`enter code here`
GO
CREATE SYNONYM dbo.GetFragStats FOR YourMaintenanceDatabase.dbo.GetFragStats;

Başka bir yol da dinamik SQL kullanmak olabilir, ancak bu da aynı anda yalnızca bir veritabanı için çalışır:

USE YourMaintenanceDatabase;
GO
CREATE PROCEDURE dbo.GetFragStats
  @DatabaseName SYSNAME,
  @tableName    NVARCHAR(128) = NULL,
  @indexID      INT           = NULL,
  @partNumber   INT           = NULL,
  @Mode         NVARCHAR(20)  = N'DETAILED'
AS
BEGIN
  SET NOCOUNT ON;

  DECLARE @sql NVARCHAR(MAX) = N'SELECT
    DatabaseName    = @DatabaseName,
    TableName       = t.name,
    IndexName       = i.name,
    IndexID         = s.index_id,
    PercentFragment = s.avg_fragmentation_in_percent,
    TotalFrags      = s.fragment_count,
    PagesPerFrag    = s.avg_fragment_size_in_pages,
    NumPages        = s.page_count,
    IndexType       = s.index_type_desc
  FROM ' + QUOTENAME(@DatabaseName) + '.sys.tables AS t
  INNER JOIN ' + QUOTENAME(@DatabaseName) + '.sys.indexes AS i
    ON t.[object_id] = i.[object_id]
    AND i.index_id = COALESCE(@indexID, i.index_id)
    AND t.name = COALESCE(@tableName, t.name)
  CROSS APPLY
    ' + QUOTENAME(@DatabaseName) + '.sys.dm_db_index_physical_stats(
        DB_ID(@DatabaseName), t.[object_id], i.index_id, @partNumber, @Mode) AS s
  WHERE s.avg_fragmentation_in_percent > 10
  ORDER BY 
    DatabaseName, TableName, IndexName, PercentFragment DESC;';

  EXEC sp_executesql @sql, 
    N'@DatabaseName SYSNAME, @tableName NVARCHAR(128), @indexID INT,
      @partNumber INT, @Mode NVARCHAR(20)',
    @DatabaseName, @tableName, @indexID, @partNumber, @Mode;
END
GO

Yine başka bir yol, tüm veritabanlarınızın tablo ve dizin adlarını birleştirmek için bir görünüm (veya tablo değerli işlev) oluşturmaktır, ancak veritabanı adlarını görünüme sabit olarak kodlamanız ve ekledikçe bunları korumanız gerekir. / bu sorguya eklenmesine izin vermek istediğiniz veritabanlarını kaldırın. Bu, diğerlerinden farklı olarak, aynı anda birden çok veritabanı için istatistikleri almanıza izin verir.

İlk olarak, görüş:

CREATE VIEW dbo.CertainTablesAndIndexes
AS
  SELECT 
    db = N'AdventureWorks2012',
    t.[object_id],
    [table] = t.name,
    i.index_id,
    [index] = i.name
  FROM AdventureWorks2012.sys.tables AS t
  INNER JOIN AdventureWorks2012.sys.indexes AS i
  ON t.[object_id] = i.[object_id]

  UNION ALL

  SELECT 
    db = N'database2',
    t.[object_id],
    [table] = t.name,
    i.index_id,
    [index] = i.name
  FROM database2.sys.tables AS t
  INNER JOIN database2.sys.indexes AS i
  ON t.[object_id] = i.[object_id]

  -- ... UNION ALL ...
  ;
GO

Sonra prosedür:

CREATE PROCEDURE dbo.GetFragStats
  @DatabaseName NVARCHAR(128) = NULL,
  @tableName    NVARCHAR(128) = NULL,
  @indexID      INT           = NULL,
  @partNumber   INT           = NULL,
  @Mode         NVARCHAR(20)  = N'DETAILED'
AS
BEGIN
  SET NOCOUNT ON;

  SELECT
    DatabaseName    = DB_NAME(s.database_id),
    TableName       = v.[table],
    IndexName       = v.[index],
    IndexID         = s.index_id,
    PercentFragment = s.avg_fragmentation_in_percent,
    TotalFrags      = s.fragment_count,
    PagesPerFrag    = s.avg_fragment_size_in_pages,
    NumPages        = s.page_count,
    IndexType       = s.index_type_desc
  FROM dbo.CertainTablesAndIndexes AS v
  CROSS APPLY sys.dm_db_index_physical_stats
    (DB_ID(v.db), v.[object_id], v.index_id, @partNumber, @Mode) AS s
  WHERE s.avg_fragmentation_in_percent > 10
    AND v.index_id = COALESCE(@indexID, v.index_id)
    AND v.[table] = COALESCE(@tableName, v.[table])
    AND v.db = COALESCE(@DatabaseName, v.db)
  ORDER BY 
    DatabaseName, TableName, IndexName, PercentFragment DESC;
END
GO

15

Kötü haberler, yakalamalarla iyi haberler ve gerçekten iyi haberler var.

Kötü haber

T-SQL nesneleri bulundukları veritabanında yürütülür. İki (çok yararlı olmayan) istisna vardır:

  1. isimleri öneki olan sp_ve [master]veritabanında bulunan saklı yordamlar (mükemmel bir seçenek değil: her seferinde bir DB, bir şey ekleme [master], muhtemelen her yeni DB için yapılması gereken her DB'ye Eş Anlamlılar ekleme)
  2. geçici saklı yordamlar - yerel ve küresel (her seferinde oluşturulması ve pratikte sp_saklanan proc ile ilgili aynı sorunları size bırakmaları için pratik bir seçenek değil [master].

İyi haber (yakalamalı)

Birçok (belki de çoğu?) Kişi gerçekten yaygın meta veriler elde etmek için yerleşik işlevlerin farkındadır:

Bu işlevleri kullanmak, sys.databases(bu gerçekten bir sorun olmasa da) JOIN'lara ( Dizinli Görünümleri hariç tuttuğunuzda sys.objectstercih edilir sys.tables) ve sys.schemas(bunu kaçırdınız ve her şeyindbo şemada değil; Ancak dört JOIN'den üçünü kaldırsak bile, işlevsel olarak aynı yerdeyiz, değil mi? Yan-lış!

OBJECT_NAME()Ve OBJECT_SCHEMA_NAME()işlevlerinin güzel özelliklerinden biri, isteğe bağlı bir ikinci parametreye sahip olmalarıdır @database_id. Yani, bu tablolara KATILMA (hariç sys.databases) veritabanına özgü olsa da, bu işlevleri kullanmak size sunucu genelinde bilgi sağlar. OBJECT_ID () bile , tam nitelenmiş bir nesne adı vererek sunucu çapında bilgi sağlar.

Bu meta-veri fonksiyonlarını ana sorguya dahil ederek, aynı zamanda mevcut veritabanının ötesine geçerek basitleştirebiliriz. Sorguyu yeniden düzenlemenin ilk geçişi bize şunları verir:

SELECT  DB_NAME(stat.database_id) AS [DatabaseName],
        OBJECT_SCHEMA_NAME(stat.[object_id], stat.database_id) AS [SchemaName],
        OBJECT_NAME(stat.[object_id], stat.database_id) AS [TableName],
        ind.name AS [IndexName],
        stat.index_id AS [IndexID],
        stat.avg_fragmentation_in_percent AS [PercentFragment],
        stat.fragment_count AS [TotalFrags],
        stat.avg_fragment_size_in_pages AS [PagesPerFrag],
        stat.page_count AS [NumPages],
        stat.index_type_desc AS [IndexType]
FROM sys.dm_db_index_physical_stats(@DatabaseID, @TableID, 
        @IndexID, @PartitionNumber, @Mode) stat
INNER JOIN sys.indexes ind
        ON ind.[object_id] = stat.[object_id]
       AND ind.[index_id] = stat.[index_id]
WHERE stat.avg_fragmentation_in_percent > 10
ORDER BY DatabaseName, TableName, IndexName, PercentFragment DESC;

Ve şimdi "catch" için: Dizin adlarını almak için, sunucu genelinde olsun, meta-veri işlevi yoktur. Öyleyse öyle mi? Veri almak için% 90 oranında tamamlandık mı ve hala belirli bir veritabanında olmamız gerekiyor sys.indexesmu? Gerçekten, ana proc'umuz her çalıştığında, sys.indexestüm veritabanlarındaki tüm girişlerin geçici bir tablosunu birleştirmek için Dinamik SQL'i kullanmak için depolanmış bir prosedür oluşturmamız gerekiyor mu? HAYIR!

Gerçekten iyi haber

Dolayısıyla, bazı insanların nefret etmeyi sevdiği küçük bir özellik geliyor, ancak düzgün kullanıldığında şaşırtıcı şeyler yapabilir. Evet: SQLCLR. Neden? SQLCLR fonksiyonları açıkça SQL ifadelerini göndermek, ancak uygulama kodundan sunma doğası gereği neden olabileceği için ise Dinamik SQL. Bu nedenle, T-SQL işlevlerinden farklı olarak, SQLCLR işlevleri yürütmeden önce sorguya bir veritabanı adı ekleyebilir. , Biz kendi işlevini oluşturabilirsiniz Anlamı yeteneğini yansıtmak için OBJECT_NAME()ve OBJECT_SCHEMA_NAME()bir almakdatabase_id ve bu veritabanı için bilgi almak.

Aşağıdaki kod bu işlevdir. Ancak kimlik yerine bir veritabanı adı alır, böylece onu aramak için fazladan bir adım atmasına gerek kalmaz (bu da biraz daha az karmaşık ve biraz daha hızlı hale getirir).

public class MetaDataFunctions
{
    [return: SqlFacet(MaxSize = 128)]
    [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = true,
        SystemDataAccess = SystemDataAccessKind.Read)]
    public static SqlString IndexName([SqlFacet(MaxSize = 128)] SqlString DatabaseName,
        SqlInt32 ObjectID, SqlInt32 IndexID)
    {
        string _IndexName = @"<unknown>";

        using (SqlConnection _Connection =
                                    new SqlConnection("Context Connection = true;"))
        {
            using (SqlCommand _Command = _Connection.CreateCommand())
            {
                _Command.CommandText = @"
SELECT @IndexName = si.[name]
FROM   [" + DatabaseName.Value + @"].[sys].[indexes] si
WHERE  si.[object_id] = @ObjectID
AND    si.[index_id] = @IndexID;
";

                SqlParameter _ParamObjectID = new SqlParameter("@ObjectID",
                                               SqlDbType.Int);
                _ParamObjectID.Value = ObjectID.Value;
                _Command.Parameters.Add(_ParamObjectID);

               SqlParameter _ParamIndexID = new SqlParameter("@IndexID", SqlDbType.Int);
                _ParamIndexID.Value = IndexID.Value;
                _Command.Parameters.Add(_ParamIndexID);

                SqlParameter _ParamIndexName = new SqlParameter("@IndexName",
                                                  SqlDbType.NVarChar, 128);
                _ParamIndexName.Direction = ParameterDirection.Output;
                _Command.Parameters.Add(_ParamIndexName);

                _Connection.Open();
                _Command.ExecuteNonQuery();

                if (_ParamIndexName.Value != DBNull.Value)
                {
                    _IndexName = (string)_ParamIndexName.Value;
                }
            }
        }

        return _IndexName;
    }
}

Dikkat ederseniz, sadece hızlı değil aynı zamanda da çalışan Bağlam Bağlantısı'nı kullanıyoruz. SAFE Montajlarda . Evet, bu şu şekilde işaretlenmiş bir Mecliste çalışıyorSAFE, bu yüzden (veya varyasyonları) Azure SQL Veritabanı V12'de bile çalışmalıdır (Nisan 2016'da SQLCLR desteği Azure SQL Veritabanından aniden kaldırıldı) .

Dolayısıyla, ana sorguyu ikinci geçişli yeniden düzenlememiz bize aşağıdakileri verir:

SELECT  DB_NAME(stat.database_id) AS [DatabaseName],
        OBJECT_SCHEMA_NAME(stat.[object_id], stat.database_id) AS [SchemaName],
        OBJECT_NAME(stat.[object_id], stat.database_id) AS [TableName],
        dbo.IndexName(DB_NAME(stat.database_id), stat.[object_id], stat.[index_id])
                     AS [IndexName],
        stat.index_id AS [IndexID],
        stat.avg_fragmentation_in_percent AS [PercentFragment],
        stat.fragment_count AS [TotalFrags],
        stat.avg_fragment_size_in_pages AS [PagesPerFrag],
        stat.page_count AS [NumPages],
        stat.index_type_desc AS [IndexType]
FROM sys.dm_db_index_physical_stats(@DatabaseID, @TableID, 
        @IndexID, @PartitionNumber, @Mode) stat
WHERE stat.avg_fragmentation_in_percent > 10
ORDER BY DatabaseName, TableName, IndexName, PercentFragment DESC;

Bu kadar! Hem bu SQLCLR Skaler UDF hem de bakım T-SQL Depolanmış Prosedürünüz aynı merkezi olarak yaşayabilir[maintenance] veritabanında . VE, bir kerede bir veritabanı işlemek zorunda değilsiniz; artık sunucu çapındaki tüm bağımlı bilgiler için meta veri işlevlerine sahipsiniz.

PS: .IsNullT-SQL sarma nesnesi WITH RETURNS NULL ON NULL INPUTseçeneği ile oluşturulması gerektiğinden, C # kodunda giriş parametrelerinin kontrolü yoktur :

CREATE FUNCTION [dbo].[IndexName]
                   (@DatabaseName [nvarchar](128), @ObjectID [int], @IndexID [int])
RETURNS [nvarchar](128) WITH EXECUTE AS CALLER, RETURNS NULL ON NULL INPUT
AS EXTERNAL NAME [{AssemblyName}].[MetaDataFunctions].[IndexName];

Ek Notlar:

  • Burada açıklanan yöntem, çapraz veritabanı meta-veri işlevlerinin eksik diğer çok benzer sorunlarını çözmek için de kullanılabilir. Aşağıdaki Microsoft Connect önerisi böyle bir duruma bir örnektir. Ve Microsoft'un "Düzeltmeyecek" olarak kapattığını görünce, OBJECT_NAME()bu ihtiyacı karşılamak gibi yerleşik işlevler sağlamakla ilgilenmedikleri açıktır (bu nedenle bu Öneri :-) tarihinde yayınlanan Geçici Çözüm :-).

    Hobt_id'den nesne adı almak için meta veri işlevi ekleyin

  • SQLCLR kullanımı hakkında daha fazla bilgi edinmek için lütfen SQL Server Central'da yazdığım Stairway to SQLCLR serisine bir göz atın (ücretsiz kayıt gereklidir; üzgünüm, bu sitenin politikalarını kontrol etmiyorum).

  • IndexName()Yukarıda gösterilen SQLCLR fonksiyonu Pastebin üzerinde kolay yüklemek komut, önceden derlenmiş mevcuttur. Komut dosyası henüz etkinleştirilmemişse ve "Derleme" olarak işaretlenmişse "CLR Tümleştirmesi" özelliğini etkinleştirir SAFE. SQL Server 2005 ve daha yeni sürümlerde (yani SQLCLR'yi destekleyen tüm sürümlerde) çalışabilmesi için .NET Framework sürüm 2.0'a göre derlenmiştir.

    SQLCLR Veritabanları arası IndexName () için meta veri işlevi

  • Herkes IndexName()SQLCLR işlevi ve 320'den fazla diğer işlevler ve saklı yordamlar ilgilenen varsa , SQL # kitaplığı (ki ben yazarım) mevcuttur. Ücretsiz sürüm varken , Sys_IndexName işlevinin yalnızca Tam sürümde (benzer bir Sys_AssemblyName işleviyle birlikte) kullanılabileceğini lütfen unutmayın .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.