sql >> データベース >  >> RDS >> Sqlserver

MSSQLServerデータベースでのインデックスの最適化の自動化

    序文

    World Wide Webは、SQLServerインデックスの最適化またはSQLServerインデックスの再構築に関する多くの情報を提供します。ただし、推奨事項のほとんどは、ロード時間が最小のデータベース(ほとんどの場合、夜間)に関するものです。

    また、データの変更と24時間年中無休の情報取得の両方に使用されるデータベースについてはどうでしょうか。

    この記事では、勤務先の会社で使用されているデータベースに実装されているSQLServerインデックスの最適化を自動化するメカニズムを提供します。インデックスの断片化は24時間年中無休のシステムで常に行われるため、このメカニズムにより、必要なインデックスを定期的に最適化できます。多くの場合、これでは1日に1回インデックスの最適化を実行するのに十分ではありません。

    解決策

    まず、一般的なアプローチを見てみましょう。

    1. フラグメント化されたインデックスとフラグメント化されたインデックスの割合を示すビューを作成します。
    2. インデックスの最適化結果を保存するためのテーブルを作成します。
    3. 選択したインデックスを分析および最適化するためのストアドプロシージャを作成します。
    4. インデックスの最適化結果の統計を表示するためのビューを作成します。
    5. 実装されたストアドプロシージャを実行するためのタスクをAgentで作成します。

    それでは、実装を見てみましょう。

    1.フラグメント化されたインデックスとフラグメント化されたインデックスの割合を示すビューを作成します。

    USE [Database_Name]
    GO
    
    SET ANSI_NULLS ON
    GO
    
    SET QUOTED_IDENTIFIER ON
    GO
    
    CREATE view [srv].[vIndexDefrag]
    as
    with info as 
    (SELECT
    	[object_id],
    	database_id,
    	index_id,
    	index_type_desc,
    	index_level,
    	fragment_count,
    	avg_fragmentation_in_percent,
    	avg_fragment_size_in_pages,
    	page_count,
    	record_count,
    	ghost_record_count
    	FROM sys.dm_db_index_physical_stats
        (DB_ID(N'Database_Name')
    	, NULL, NULL, NULL ,
    	N'DETAILED')
    	where index_level = 0
    	)
    SELECT
    	b.name as db,
    	s.name as shema,
    	t.name as tb,
    	i.index_id as idx,
    	i.database_id,
    	idx.name as index_name,
    	i.index_type_desc,i.index_level as [level],
    	i.[object_id],
    	i.fragment_count as frag_num,
    	round(i.avg_fragmentation_in_percent,2) as frag,
    	round(i.avg_fragment_size_in_pages,2) as frag_page,
    	i.page_count as [page],
    	i.record_count as rec,
    	i.ghost_record_count as ghost,
    	round(i.avg_fragmentation_in_percent*i.page_count,0) as func
    FROM Info as i
    inner join [sys].[databases]	as b	on i.database_id = b.database_id
    inner join [sys].[all_objects]	as t	on i.object_id = t.object_id
    inner join [sys].[schemas]	as s	on t.[schema_id] = s.[schema_id]
    inner join [sys].[indexes]	as idx on t.object_id = idx.object_id and idx.index_id = i.index_id
     where i.avg_fragmentation_in_percent >= 30 and i.index_type_desc <> 'HEAP';
    GO

    このビューには、断片化の割合が30を超えるインデックス、つまり最適化が必要なインデックスのみが表示されます。ヒープではないインデックスのみが表示されます。後者のインデックスは、そのようなヒープのブロックや、さらにインデックスの断片化などの悪影響をもたらす可能性があるためです。

    このビューは、重要なシステムビューsys.dm_db_index_physical_statsを使用します。

    2.インデックスデフラグの結果を保存するためのテーブルを作成します。

    USE [Database_Name]
    GO
    
    SET ANSI_NULLS ON
    GO
    
    SET QUOTED_IDENTIFIER ON
    GO
    
    CREATE TABLE [srv].[Defrag](
    	[ID] [bigint] IDENTITY(794,1) NOT NULL,
    	[db] [nvarchar](100) NULL,
    	[shema] [nvarchar](100) NULL,
    	[table] [nvarchar](100) NULL,
    	[IndexName] [nvarchar](100) NULL,
    	[frag_num] [int] NULL,
    	[frag] [decimal](6, 2) NULL,
    	[page] [int] NULL,
    	[rec] [int] NULL,
            [func] [int] NULL,
    	[ts] [datetime] NULL,
    	[tf] [datetime] NULL,
    	[frag_after] [decimal](6, 2) NULL,
    	[object_id] [int] NULL,
    	[idx] [int] NULL,
    	[InsertUTCDate] [datetime] NOT NULL,
     CONSTRAINT [PK_Defrag] PRIMARY KEY CLUSTERED 
    (
    	[ID] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY];
    GO
    
    ALTER TABLE [srv].[Defrag] ADD  CONSTRAINT [DF_Defrag_InsertUTCDate]  DEFAULT (getutcdate()) FOR [InsertUTCDate];
    GO

    このテーブルで最も重要なことは、データの削除を念頭に置くことです(たとえば、1か月以上前のデータ)。

    テーブルフィールドは次のポイントから理解できるようになります。

    3.選択したインデックスを分析および最適化するためのストアドプロシージャを作成します。

    USE [Database_Name]
    GO
    
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    
    CREATE PROCEDURE [srv].[AutoDefragIndex]
    AS
    BEGIN
    	SET NOCOUNT ON;
    
    	--declaring required variables
    	declare @IndexName nvarchar(100) --index name
    	,@db nvarchar(100)			 --database name
    	,@Shema nvarchar(100)			 --schema name
    	,@Table nvarchar(100)			 --table name
    	,@SQL_Str nvarchar (2000)		 --string for command generation
    	,@frag decimal(6,2)				 --fragmentation percentage before defragmentation
    	,@frag_after decimal(6,2)		 --fragmentation percentage after defragmentation
            --Number of fragments at the final level of the IN_ROW_DATA allocation unit
            ,@frag_num int				 
    	,@func int					 --round(i.avg_fragmentation_in_percent*i.page_count,0)
    	,@page int					 --number of index pages  
    	,@rec int						 --total number of records
    	,@ts datetime					 --date and time of defragmentation start
    	,@tf datetime					 --date and time of defragmenation finish
    	--Table or view object ID for which the index was created
            ,@object_id int					 
    	,@idx int;						 --index ID
    
    	--getting current date and time
    	set @ts = getdate();
    	
    	--getting next index for defragmenation
    	--Here the important index is selected. At that, a situation when one index is defragmented regularly, while other indexes are not selected for defragmentation is unlikely.
    	select top 1
    		@IndexName = index_name,
    		@db=db,
    		@Shema = shema,
    		@Table = tb,
    		@frag = frag,
    		@frag_num = frag_num,
    		@func=func,
    		@page =[page],
    		@rec = rec,
    		@object_id = [object_id],
    		@idx = idx 
    	from  [srv].[vIndexDefrag]
    	order by func*power((1.0-
    	  convert(float,(select count(*) from SRV.[srv].[Defrag] vid where vid.db=db 
    														 and vid.shema = shema
    														 and vid.[table] = tb
    														 and vid.IndexName = index_name))
    	 /
    	 convert(float,
                      case  when (exists (select top 1 1 from SRV.[srv].[Defrag] vid1 where vid1.db=db))
                                then (select count(*) from  SRV.[srv].[Defrag] vid1 where vid1.db=db)
                                else 1.0 end))
                        ,3) desc
    
    	--if we get such index
    	if(@db is not null)
    	begin
    	   --index reorganization
    	   set @SQL_Str = 'alter index ['[email protected]+'] on ['[email protected]+'].['[email protected]+'] Reorganize';
    
    		execute sp_executesql  @SQL_Str;
    
    		--getting current date and time
    		set @tf = getdate()
    
    		--getting fragmentation percentage after defragmentation
    		SELECT @frag_after = avg_fragmentation_in_percent
    		FROM sys.dm_db_index_physical_stats
    			(DB_ID(@db), @object_id, @idx, NULL ,
    			N'DETAILED')
    		where index_level = 0;
    
    		--writing the result of work
    		insert into SRV.srv.Defrag(
    									[db],
    									[shema],
    									[table],
    									[IndexName],
    									[frag_num],
    									[frag],
    									[page],
    									[rec],
    									ts,
    									tf,
    									frag_after,
    									object_id,
    									idx
    								  )
    						select
    									@db,
    									@shema,
    									@table,
    									@IndexName,
    									@frag_num,
    									@frag,
    									@page,
    									@rec,
    									@ts,
    									@tf,
    									@frag_after,
    									@object_id,
    									@idx;
    		
    		--upating statistics for index
    		set @SQL_Str = 'UPDATE STATISTICS ['[email protected]+'].['[email protected]+'] ['[email protected]+']';
    
    		execute sp_executesql  @SQL_Str;
    	end
    END

    4.インデックスの最適化結果の統計を表示するためのビューを作成します。

    USE [Database_Name]
    GO
    
    SET ANSI_NULLS ON
    GO
    
    SET QUOTED_IDENTIFIER ON
    GO
    
    CREATE view [srv].[vStatisticDefrag] as
    SELECT top 1000
    	  [db]
    	  ,[shema]
              ,[table]
              ,[IndexName]
              ,avg([frag]) as AvgFrag
              ,avg([frag_after]) as AvgFragAfter
    	  ,avg(page) as AvgPage
      FROM [srv].[Defrag]
      group by [db], [shema], [table], [IndexName]
      order by abs(avg([frag])-avg([frag_after])) desc;
    GO

    このビューを使用して、インデックスの最適化の自動化の結果について管理者に毎日通知できます。

    5.実装されたストアドプロシージャを実行するためのタスクをAgentで作成する

    ここでは、実験的な方法で時間を選択する必要があります。私の場合、どこかで5分、どこかで–1時間かかりました。

    このアルゴリズムは複数のデータベースで拡張できますが、この場合、追加のポイント6が必要です:

    インデックスの最適化の自動化に関するすべての統計を1か所に集めて、後で管理者に送信できるようにします。

    そして今、私はすでに提供されているインデックスサポートの推奨事項について詳しく説明したいと思います:

    1. インデックスは常に断片化されており、データベースがアイドル状態のままになる時間はほとんどないため、データベースの最小負荷時にすべてのインデックスを同時に最適化することは、24時間年中無休のシステムでは受け入れられません。
    2. SQL Serverインデックスの再編成–この操作は、テーブルまたはパーティション(パーティションインデックスの場合)をブロックします。これは、24時間年中無休のシステムには適していません。次に、リアルタイムモードでのインデックスの再構築は、エンタープライズソリューションでのみサポートされ、データの損傷につながる可能性もあります。

    この方法は最適ではありませんが、実行計画を作成するためのオプティマイザーによるその後の使用のために、インデックスが適切にデフラグされていること(フラグメント化の30〜40%を超えないこと)を確実に処理できます。

    このアプローチの合理的な長所と短所、およびテスト済みの代替案についてのコメントに感謝します。

    参照

    • インデックスの再編成と再構築
    • sys.dm_db_index_physical_stats

    便利なツール:

    dbForge Index Manager – SQLインデックスのステータスを分析し、インデックスの断片化に関する問題を修正するための便利なSSMSアドイン。


    1. オフセットなしおよびオフセット対応の日時を減算することはできません

    2. Lumen-実行時にデータベース接続を作成します

    3. バイナリデータを格納するためのVarBinaryとImageSQLServerのデータ型?

    4. 2つの配列を組み合わせたPostgreSQLのzip()関数のようなものはありますか?