Sqlserver
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Sqlserver

Tự động chống phân mảnh chỉ mục trong cơ sở dữ liệu MS SQL Server

Lời nói đầu

World Wide Web cung cấp một loạt thông tin về chống phân mảnh chỉ mục SQL Server hoặc xây dựng lại chỉ mục SQL Server. Tuy nhiên, hầu hết các đề xuất đề cập đến cơ sở dữ liệu có thời gian tải tối thiểu (chủ yếu là vào ban đêm).

Và cơ sở dữ liệu được sử dụng cho cả hai, sửa đổi dữ liệu và truy xuất thông tin trên cơ sở 24/7 thì sao?

Trong bài viết này, tôi sẽ cung cấp một cơ chế để tự động chống phân mảnh chỉ mục SQL Server được thực hiện trong cơ sở dữ liệu được sử dụng trong công ty tôi đang làm việc. Cơ chế này cho phép chống phân mảnh các chỉ mục được yêu cầu một cách thường xuyên vì sự phân mảnh chỉ mục diễn ra liên tục trong hệ thống 24/7. Thông thường, điều này là không đủ để thực hiện chống phân mảnh chỉ mục một lần một ngày.

Giải pháp

Trước tiên, chúng ta hãy xem xét cách tiếp cận chung:

  1. Tạo chế độ xem hiển thị các chỉ mục nào đã được phân mảnh và tỷ lệ phần trăm của các chỉ mục bị phân mảnh.
  2. Tạo một bảng để lưu trữ kết quả chống phân mảnh chỉ mục.
  3. Tạo một quy trình được lưu trữ để phân tích và chống phân mảnh chỉ mục đã chọn.
  4. Tạo chế độ xem để xem thống kê kết quả chống phân mảnh chỉ mục.
  5. Tạo một nhiệm vụ trong Agent để chạy quy trình được lưu trữ đã triển khai.

Và bây giờ, hãy xem cách triển khai:

1. Tạo một dạng xem hiển thị các chỉ mục nào đã được phân mảnh và tỷ lệ phần trăm của các chỉ mục bị phân mảnh:

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

Chế độ xem này chỉ hiển thị các chỉ mục có phần trăm phân mảnh lớn hơn 30, tức là các chỉ mục yêu cầu chống phân mảnh. Nó chỉ hiển thị các chỉ mục không phải là đống, vì những chỉ mục sau có thể dẫn đến các tác động tiêu cực, như chặn một đống như vậy hoặc phân mảnh chỉ mục hơn nữa.

Chế độ xem sử dụng chế độ xem hệ thống quan trọng sys.dm_db_index_physical_stats.

2. Tạo bảng để lưu trữ kết quả chống phân mảnh chỉ mục:

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

Điều quan trọng nhất của bảng này là lưu ý việc xóa dữ liệu (ví dụ:dữ liệu cũ hơn 1 tháng).

Các trường trong bảng sẽ trở nên dễ hiểu từ điểm tiếp theo.

3. Tạo một thủ tục được lưu trữ để phân tích và chống phân mảnh chỉ mục đã chọn:

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. Tạo khung nhìn để xem thống kê kết quả chống phân mảnh chỉ mục:

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

Chế độ xem này có thể được sử dụng để thông báo cho quản trị viên hàng ngày về kết quả của quá trình tự động hóa chống phân mảnh chỉ mục.

5. Tạo một nhiệm vụ trong Agent để chạy quy trình được lưu trữ đã triển khai

Ở đây, chúng ta cần chọn thời gian theo cách thử nghiệm. Trong trường hợp của tôi, ở đâu đó tôi có 5 phút, ở đâu đó - 1 giờ.

Thuật toán này có thể được mở rộng trên một số cơ sở dữ liệu, nhưng trong trường hợp này, chúng ta cần thêm một điểm 6:

Thu thập tất cả các số liệu thống kê của tự động chống phân mảnh chỉ mục vào một nơi để gửi tới quản trị viên sau đó.

Và bây giờ, tôi muốn tập trung vào các khuyến nghị đã được cung cấp để hỗ trợ chỉ mục:

  1. Việc chống phân mảnh đồng thời tất cả các chỉ mục trong thời gian tải cơ sở dữ liệu tối thiểu là không thể chấp nhận được đối với hệ thống 24/7, vì các chỉ mục liên tục bị phân mảnh và hầu như không có thời gian khi cơ sở dữ liệu không hoạt động.
  2. Tổ chức lại chỉ mục SQL Server - thao tác này chặn một bảng hoặc phân vùng (trong trường hợp chỉ mục được phân vùng), điều này không tốt cho hệ thống 24/7. Sau đó, việc xây dựng lại chỉ mục ở chế độ thời gian thực chỉ được hỗ trợ trong giải pháp Enterprise và cũng có thể dẫn đến việc làm hỏng dữ liệu.

Phương pháp này không phải là tối ưu, nhưng nó có thể đối phó thành công trong việc đảm bảo rằng các chỉ mục được chống phân mảnh đúng cách (không vượt quá 30-40% phân mảnh) cho việc sử dụng tiếp theo của chúng bởi trình tối ưu hóa để xây dựng kế hoạch thực thi.

Tôi sẽ biết ơn những nhận xét của bạn với những ưu và nhược điểm hợp lý của phương pháp này, cũng như những đề xuất thay thế đã được thử nghiệm.

Tài liệu tham khảo

  • Tổ chức lại và xây dựng lại các chỉ mục
  • sys.dm_db_index_physical_stats

Công cụ hữu ích:

dbForge Index Manager - phần bổ trợ SSMS tiện dụng để phân tích trạng thái của chỉ mục SQL và khắc phục sự cố với phân mảnh chỉ mục.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Mệnh đề WHERE thực thi tốt hơn trước IN và JOIN hoặc sau đó

  2. Cột không hợp lệ trong danh sách chọn vì nó không được chứa trong hàm tổng hợp hoặc mệnh đề GROUP BY

  3. TABLESAMPLE trả về số hàng sai?

  4. Hiểu Bí danh cột trong Truy vấn Chọn trong SQL Server - Hướng dẫn SQL Server / TSQL Phần 115

  5. Ảnh chụp nhanh cơ sở dữ liệu SQL Server -4