sys.dm_db_index_physical_stats
ビューを使用して、カスタマイズされたメンテナンスソリューションに取り組んでいます。現在、ストアドプロシージャから参照されています。このストアドプロシージャが私のデータベースの1つで実行されると、私がやりたいことを実行し、任意のデータベースに関するすべてのレコードのリストをプルダウンします。別のデータベースに配置すると、そのDBのみに関連するすべてのレコードのリストがプルダウンされます。
例(下のコード):
特にデータベース3でこの手順が必要な理由は、すべてのメンテナンスオブジェクトを同じデータベース内に保持したいためです。この仕事をメンテナンスデータベースに入れて、あたかもそのアプリケーションデータベースにあるかのように機能させたいです。
コード:
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
1つの方法は、master
にシステムプロシージャを作成し、メンテナンスデータベースにラッパーを作成することです。これは、一度に1つのデータベースに対してのみ機能することに注意してください。
まず、マスターで:
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
次に、メンテナンスデータベースで、動的SQLを使用してコンテキストを正しく設定するラッパーを作成します。
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
(データベース名を実際にNULL
にできないのは、sys.objects
やsys.indexes
などは、各データベースに独立して存在するため、それらに参加できないためです。インスタンス全体の情報が必要な場合は、別の手順を実行してください。)
これで、他のデータベース、たとえば.
EXEC YourMaintenanceDatabase.dbo.GetFragStats
@DatabaseName = N'AdventureWorks2012',
@TableName = N'SalesOrderHeader';
また、各データベースで常にsynonym
を作成できるため、メンテナンスデータベースの名前を参照する必要もありません。
USE SomeOtherDatabase;`enter code here`
GO
CREATE SYNONYM dbo.GetFragStats FOR YourMaintenanceDatabase.dbo.GetFragStats;
もう1つの方法は動的SQLを使用することですが、これも一度に1つのデータベースに対してのみ機能します。
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
さらに別の方法は、すべてのデータベースのテーブル名とインデックス名を結合するビュー(またはテーブル値関数)を作成することですが、データベース名をビューにハードコードし、追加するときにそれらを維持する必要があります。このクエリに含めることを許可する/ removeデータベース。これにより、他のデータベースとは異なり、複数のデータベースの統計を一度に取得できます。
まず、ビュー:
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
次に手順:
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
まあ、悪いニュース、キャッチ付きの良いニュース、そしていくつかの本当に良いニュースがあります。
悪いニュース
T-SQLオブジェクトは、それらが存在するデータベースで実行されます。例外が2つあります(あまり役に立ちません)。
sp_
で始まる名前が付けられ、[master]
データベースに存在するストアドプロシージャ(優れたオプションではありません。一度に1つのDBを[master]
に追加し、各DBにシノニムを追加します。新しいDBごとに実行する必要があります)sp_
の[master]
ストアドプロシージャと同じ問題が発生するため、実用的なオプションではありません。良いニュース(キャッチ付き)
多くの(おそらくほとんど?)人々は、いくつかの本当に一般的なメタデータを取得するための組み込み関数を知っています。
これらの関数を使用すると、sys.databases
(これは実際には問題ではありません)、sys.objects
(インデックス付きビューを除外するsys.tables
よりも優先されます)、およびsys.schemas
(不足しているため、dbo
にすべてではない)へのJOINの必要性を排除できます。スキーマ;-)。しかし、4つのJOINのうち3つを削除しても、機能的には同じ場所です。違います!
OBJECT_NAME()
関数とOBJECT_SCHEMA_NAME()
関数の優れた機能の1つは、@database_id
のオプションの2番目のパラメーターがあることです。つまり、これらのテーブルへのJOIN処理(sys.databases
を除く)はデータベース固有ですが、これらの関数を使用すると、サーバー全体の情報を取得できます。でも OBJECT_ID() は、完全修飾オブジェクト名を指定することにより、サーバー全体の情報を可能にします。
これらのメタデータ関数をメインクエリに組み込むことにより、単純化しながら、現在のデータベースを超えて拡張できます。クエリのリファクタリングの最初のパスにより、次のようになります。
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;
そして今、「キャッチ」のために:サーバー全体のインデックスは言うまでもなく、インデックス名を取得するメタデータ関数はありません。それですか?私たちは90%完了していますが、sys.indexes
データを取得するために特定のデータベースにアクセスする必要があるのですか。動的SQLを使用して、メインプロシージャが実行されるたびに、JOINできるようにすべてのデータベースのすべてのsys.indexes
エントリの一時テーブルを作成するストアドプロシージャを作成する必要がありますか?番号!
本当に良いニュース
そのため、嫌いな人もいる小さな機能がありますが、適切に使用すると、驚くべきことができるようになります。はい、SQLCLR。どうして? SQLCLR関数は明らかにSQLステートメントを送信できますが、アプリコードから送信するという性質上、is動的SQLです。したがって、T-SQL関数とは異なり、SQLCLR関数はクエリを実行する前にデータベース名をクエリに挿入できます。つまり、OBJECT_NAME()
およびOBJECT_SCHEMA_NAME()
がdatabase_id
を取得してそのデータベースの情報を取得する機能を反映する独自の関数を作成できます。
次のコードはその関数です。ただし、IDの代わりにデータベース名を使用するため、検索する追加の手順を実行する必要はありません(これにより、少し複雑で少し速くなります)。
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;
}
}
気がつくと思いますが、コンテキスト接続を使用しています。これは高速であるだけでなく、SAFE
アセンブリでも機能します。はい、これはSAFE
としてマークされたアセンブリで機能しますなので、Azure SQL Database V12でも動作するはずです(またはそのバリエーション)(SQLCLRのサポートは、2016年4月にAzure SQL Databaseから突然ではなく削除されました)。
したがって、メインクエリの2回目のパスリファクタリングでは、次のようになります。
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;
それでおしまい!このSQLCLRスカラーUDFとメンテナンスT-SQLストアドプロシージャは、同じ集中管理された[maintenance]
データベースに格納できます。また、一度に1つのデータベースを処理する必要はありません。これで、サーバー全体のすべての依存情報のメタデータ関数ができました。
追伸T-SQLラッパーオブジェクトは.IsNull
オプションを使用して作成する必要があるため、C#コードの入力パラメーターのWITH RETURNS NULL ON NULL INPUT
チェックはありません。
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];
追記:
ここで説明する方法は、クロスデータベースメタデータ関数がないという他の非常に類似した問題を解決するためにも使用できます。次のMicrosoft Connectの提案は、このようなケースの例です。そして、Microsoftがそれを「修正しない」として閉じていることを見て、このニーズを満たすためにOBJECT_NAME()
などの組み込み関数を提供することに関心がないことは明らかです(そのため、回避策が投稿されています)提案:-)。
SQLCLRの使用の詳細については、SQL Server Centralで作成している Stairway to SQLCLR シリーズをご覧ください(無料の登録が必要です。申し訳ありませんが、そのサイトのポリシーは管理していません)。 。
上記のIndexName()
SQLCLR関数は、Pastebinに簡単にインストールできるスクリプトで、事前にコンパイルして利用できます。 "CLR統合"機能がまだ有効になっておらず、アセンブリがSAFE
としてマークされている場合、スクリプトはそれを有効にします。 SQL Server 2005以降(つまり、SQLCLRをサポートするすべてのバージョン)で動作するように、.NET Frameworkバージョン2.0に対してコンパイルされています。
誰かがIndexName()
SQLCLR関数and 320を超える他の関数とストアドプロシージャに興味がある場合は、 SQL# ライブラリ(これはの作者)。無料版はありますが、Sys_IndexName関数は完全版でのみ使用できます(同様のSys_AssemblyName関数)。