نظرة عامة على أداة SQLIndexManager المجانية

كما تعلمون ، تلعب الفهارس دورًا مهمًا في نظام إدارة قواعد البيانات (DBMS) ، حيث توفر بحثًا سريعًا عن السجلات اللازمة. لذلك ، من المهم جدًا خدمتهم في الوقت المناسب. لقد كتب الكثير من المواد عن التحليل والتحسين ، بما في ذلك على الإنترنت. على سبيل المثال ، تم إجراء مراجعة حديثة لهذا الموضوع في هذا المنشور .

هناك العديد من الحلول المدفوعة والمجانية لهذا الغرض. على سبيل المثال ، يوجد حل جاهز يعتمد على طريقة تحسين الفهرس التكيفي.

بعد ذلك ، خذ بعين الاعتبار الأداة المساعدة SQLIndexManager المجانية ، التي ألفها AlanDenton .

يتكون الفرق الفني الرئيسي بين SQLIndexManager وعدد من نظائرها الأخرى من قبل المؤلف نفسه هنا وهنا .

في نفس المقالة ، نلقي نظرة على المشروع وإمكانيات استخدام حل البرنامج هذا.

ناقش هذه الأداة هنا .
بمرور الوقت ، تم إصلاح معظم التعليقات والأخطاء.

لذا ، دعنا الآن ننتقل إلى الأداة المساعدة SQLIndexManager نفسها.

تتم كتابة التطبيق في C # .NET Framework 4.5 في Visual Studio 2017 ويستخدم DevExpress للنماذج:



ويبدو مثل هذا:



يتم إنشاء جميع الطلبات في الملفات التالية:

  1. مؤشر
  2. سؤال
  3. QueryEngine
  4. ServerInfo



عند الاتصال بقاعدة البيانات وإرسال الطلبات إلى DBMS ، يتم توقيع التطبيق على النحو التالي:

ApplicationName=”SQLIndexManager” 

عند بدء تشغيل التطبيق ، يتم فتح نافذة مشروطة لإضافة اتصال:


هنا التحميل من القائمة الكاملة لجميع مثيلات MS SQL Server المتاحة عبر الشبكات المحلية لا يعمل حتى الآن.

يمكنك أيضًا إضافة اتصال باستخدام الزر الموجود في أقصى اليسار في القائمة الرئيسية:



بعد ذلك ، سيتم إطلاق استعلامات قواعد البيانات التالية:

  1. الحصول على معلومات DBMS
     SELECT ProductLevel = SERVERPROPERTY('ProductLevel') , Edition = SERVERPROPERTY('Edition') , ServerVersion = SERVERPROPERTY('ProductVersion') , IsSysAdmin = CAST(IS_SRVROLEMEMBER('sysadmin') AS BIT) 

  2. الحصول على قائمة قواعد البيانات المتاحة مع خصائصها وجيزة
     SELECT DatabaseName = t.[name] , d.DataSize , DataUsedSize = CAST(NULL AS BIGINT) , d.LogSize , LogUsedSize = CAST(NULL AS BIGINT) , RecoveryModel = t.recovery_model_desc , LogReuseWait = t.log_reuse_wait_desc FROM sys.databases t WITH(NOLOCK) LEFT JOIN ( SELECT [database_id] , DataSize = SUM(CASE WHEN [type] = 0 THEN CAST(size AS BIGINT) END) , LogSize = SUM(CASE WHEN [type] = 1 THEN CAST(size AS BIGINT) END) FROM sys.master_files WITH(NOLOCK) GROUP BY [database_id] ) d ON d.[database_id] = t.[database_id] WHERE t.[state] = 0 AND t.[database_id] != 2 AND ISNULL(HAS_DBACCESS(t.[name]), 1) = 1 


بعد تنفيذ البرامج النصية أعلاه ، تظهر نافذة تحتوي على معلومات مختصرة حول قواعد البيانات الخاصة بمثيل MS SQL Server المحدد:



تجدر الإشارة إلى أن المعلومات الموسعة يتم عرضها بناءً على الحقوق. إذا كان هناك مسؤول النظام ، فيمكنك تحديد البيانات من طريقة العرض sys.master_files . إذا لم يكن هناك مثل هذه الحقوق ، فسيتم إرجاع بيانات أقل حتى لا يتم إبطاء الطلب.

هنا تحتاج إلى اختيار قاعدة البيانات المثيرة للاهتمام والنقر على زر "موافق".

بعد ذلك ، سيتم تنفيذ البرنامج النصي التالي لكل قاعدة بيانات محددة لتحليل حالة الفهارس:

تحليل حالة الفهرس
 declare @Fragmentation float=15; declare @MinIndexSize bigint=768; declare @MaxIndexSize bigint=1048576; declare @PreDescribeSize bigint=32768; SET NOCOUNT ON SET ARITHABORT ON SET NUMERIC_ROUNDABORT OFF IF OBJECT_ID('tempdb.dbo.#AllocationUnits') IS NOT NULL DROP TABLE #AllocationUnits CREATE TABLE #AllocationUnits ( ContainerID BIGINT PRIMARY KEY , ReservedPages BIGINT NOT NULL , UsedPages BIGINT NOT NULL ) INSERT INTO #AllocationUnits (ContainerID, ReservedPages, UsedPages) SELECT [container_id] , SUM([total_pages]) , SUM([used_pages]) FROM sys.allocation_units WITH(NOLOCK) GROUP BY [container_id] HAVING SUM([total_pages]) BETWEEN @MinIndexSize AND @MaxIndexSize IF OBJECT_ID('tempdb.dbo.#ExcludeList') IS NOT NULL DROP TABLE #ExcludeList CREATE TABLE #ExcludeList (ID INT PRIMARY KEY) INSERT INTO #ExcludeList SELECT [object_id] FROM sys.objects WITH(NOLOCK) WHERE [type] IN ('V', 'U') AND ( [is_ms_shipped] = 1 ) IF OBJECT_ID('tempdb.dbo.#Partitions') IS NOT NULL DROP TABLE #Partitions SELECT [object_id] , [index_id] , [partition_id] , [partition_number] , [rows] , [data_compression] INTO #Partitions FROM sys.partitions WITH(NOLOCK) WHERE [object_id] > 255 AND [rows] > 0 AND [object_id] NOT IN (SELECT * FROM #ExcludeList) IF OBJECT_ID('tempdb.dbo.#Indexes') IS NOT NULL DROP TABLE #Indexes CREATE TABLE #Indexes ( ObjectID INT NOT NULL , IndexID INT NOT NULL , IndexName SYSNAME NULL , PagesCount BIGINT NOT NULL , UnusedPagesCount BIGINT NOT NULL , PartitionNumber INT NOT NULL , RowsCount BIGINT NOT NULL , IndexType TINYINT NOT NULL , IsAllowPageLocks BIT NOT NULL , DataSpaceID INT NOT NULL , DataCompression TINYINT NOT NULL , IsUnique BIT NOT NULL , IsPK BIT NOT NULL , FillFactorValue INT NOT NULL , IsFiltered BIT NOT NULL , PRIMARY KEY (ObjectID, IndexID, PartitionNumber) ) INSERT INTO #Indexes SELECT ObjectID = i.[object_id] , IndexID = i.index_id , IndexName = i.[name] , PagesCount = a.ReservedPages , UnusedPagesCount = CASE WHEN ABS(a.ReservedPages - a.UsedPages) > 32 THEN a.ReservedPages - a.UsedPages ELSE 0 END , PartitionNumber = p.[partition_number] , RowsCount = ISNULL(p.[rows], 0) , IndexType = i.[type] , IsAllowPageLocks = i.[allow_page_locks] , DataSpaceID = i.[data_space_id] , DataCompression = p.[data_compression] , IsUnique = i.[is_unique] , IsPK = i.[is_primary_key] , FillFactorValue = i.[fill_factor] , IsFiltered = i.[has_filter] FROM #AllocationUnits a JOIN #Partitions p ON a.ContainerID = p.[partition_id] JOIN sys.indexes i WITH(NOLOCK) ON i.[object_id] = p.[object_id] AND p.[index_id] = i.[index_id] WHERE i.[type] IN (0, 1, 2, 5, 6) AND i.[object_id] > 255 DECLARE @files TABLE (ID INT PRIMARY KEY) INSERT INTO @files SELECT DISTINCT [data_space_id] FROM sys.database_files WITH(NOLOCK) WHERE [state] != 0 AND [type] = 0 IF @@ROWCOUNT > 0 BEGIN DELETE FROM i FROM #Indexes i LEFT JOIN sys.destination_data_spaces dds WITH(NOLOCK) ON i.DataSpaceID = dds.[partition_scheme_id] AND i.PartitionNumber = dds.[destination_id] WHERE ISNULL(dds.[data_space_id], i.DataSpaceID) IN (SELECT * FROM @files) END DECLARE @DBID INT , @DBNAME SYSNAME SET @DBNAME = DB_NAME() SELECT @DBID = [database_id] FROM sys.databases WITH(NOLOCK) WHERE [name] = @DBNAME IF OBJECT_ID('tempdb.dbo.#Fragmentation') IS NOT NULL DROP TABLE #Fragmentation CREATE TABLE #Fragmentation ( ObjectID INT NOT NULL , IndexID INT NOT NULL , PartitionNumber INT NOT NULL , Fragmentation FLOAT NOT NULL , PRIMARY KEY (ObjectID, IndexID, PartitionNumber) ) INSERT INTO #Fragmentation (ObjectID, IndexID, PartitionNumber, Fragmentation) SELECT i.ObjectID , i.IndexID , i.PartitionNumber , r.[avg_fragmentation_in_percent] FROM #Indexes i CROSS APPLY sys.dm_db_index_physical_stats(@DBID, i.ObjectID, i.IndexID, i.PartitionNumber, 'LIMITED') r WHERE i.PagesCount <= @PreDescribeSize AND r.[index_level] = 0 AND r.[alloc_unit_type_desc] = 'IN_ROW_DATA' AND i.IndexType IN (0, 1, 2) IF OBJECT_ID('tempdb.dbo.#Columns') IS NOT NULL DROP TABLE #Columns CREATE TABLE #Columns ( ObjectID INT NOT NULL , ColumnID INT NOT NULL , ColumnName SYSNAME NULL , SystemTypeID TINYINT NULL , IsSparse BIT , IsColumnSet BIT , MaxLen INT , PRIMARY KEY (ObjectID, ColumnID) ) INSERT INTO #Columns SELECT ObjectID = [object_id] , ColumnID = [column_id] , ColumnName = [name] , SystemTypeID = [system_type_id] , IsSparse = [is_sparse] , IsColumnSet = [is_column_set] , MaxLen = [max_length] FROM sys.columns WITH(NOLOCK) WHERE [object_id] IN (SELECT DISTINCT i.ObjectID FROM #Indexes i) IF OBJECT_ID('tempdb.dbo.#IndexColumns') IS NOT NULL DROP TABLE #IndexColumns CREATE TABLE #IndexColumns ( ObjectID INT NOT NULL , IndexID INT NOT NULL , OrderID INT NOT NULL , ColumnID INT NOT NULL , IsIncluded BIT NOT NULL , PRIMARY KEY (ObjectID, IndexID, ColumnID) ) INSERT INTO #IndexColumns SELECT ObjectID = [object_id] , IndexID = [index_id] , OrderID = CASE WHEN [is_included_column] = 0 THEN [key_ordinal] ELSE [index_column_id] END , ColumnID = [column_id] , IsIncluded = ISNULL([is_included_column], 0) FROM sys.index_columns ic WITH(NOLOCK) WHERE EXISTS( SELECT * FROM #Indexes i WHERE i.ObjectID = ic.[object_id] AND i.IndexID = ic.[index_id] AND i.IndexType IN (1, 2) ) IF OBJECT_ID('tempdb.dbo.#Lob') IS NOT NULL DROP TABLE #Lob CREATE TABLE #Lob ( ObjectID INT NOT NULL , IndexID INT NOT NULL , IsLobLegacy BIT , IsLob BIT , PRIMARY KEY (ObjectID, IndexID) ) INSERT INTO #Lob (ObjectID, IndexID, IsLobLegacy, IsLob) SELECT c.ObjectID , IndexID = ISNULL(i.IndexID, 1) , IsLobLegacy = MAX(CASE WHEN c.SystemTypeID IN (34, 35, 99) THEN 1 END) , IsLob = 0 FROM #Columns c LEFT JOIN #IndexColumns i ON c.ObjectID = i.ObjectID AND c.ColumnID = i.ColumnID WHERE c.SystemTypeID IN (34, 35, 99) GROUP BY c.ObjectID , i.IndexID IF OBJECT_ID('tempdb.dbo.#Sparse') IS NOT NULL DROP TABLE #Sparse CREATE TABLE #Sparse (ObjectID INT PRIMARY KEY) INSERT INTO #Sparse SELECT DISTINCT ObjectID FROM #Columns WHERE IsSparse = 1 OR IsColumnSet = 1 IF OBJECT_ID('tempdb.dbo.#AggColumns') IS NOT NULL DROP TABLE #AggColumns CREATE TABLE #AggColumns ( ObjectID INT NOT NULL , IndexID INT NOT NULL , IndexColumns NVARCHAR(MAX) , IncludedColumns NVARCHAR(MAX) , PRIMARY KEY (ObjectID, IndexID) ) INSERT INTO #AggColumns SELECT t.ObjectID , t.IndexID , IndexColumns = STUFF(( SELECT ', [' + c.ColumnName + ']' FROM #IndexColumns i JOIN #Columns c ON i.ObjectID = c.ObjectID AND i.ColumnID = c.ColumnID WHERE i.ObjectID = t.ObjectID AND i.IndexID = t.IndexID AND i.IsIncluded = 0 ORDER BY i.OrderID FOR XML PATH(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 2, '') , IncludedColumns = STUFF(( SELECT ', [' + c.ColumnName + ']' FROM #IndexColumns i JOIN #Columns c ON i.ObjectID = c.ObjectID AND i.ColumnID = c.ColumnID WHERE i.ObjectID = t.ObjectID AND i.IndexID = t.IndexID AND i.IsIncluded = 1 ORDER BY i.OrderID FOR XML PATH(''), TYPE).value('(./text())[1]', 'NVARCHAR(MAX)'), 1, 2, '') FROM ( SELECT DISTINCT ObjectID, IndexID FROM #Indexes WHERE IndexType IN (1, 2) ) t SELECT i.ObjectID , i.IndexID , i.IndexName , ObjectName = o.[name] , SchemaName = s.[name] , i.PagesCount , i.UnusedPagesCount , i.PartitionNumber , i.RowsCount , i.IndexType , i.IsAllowPageLocks , u.TotalWrites , u.TotalReads , u.TotalSeeks , u.TotalScans , u.TotalLookups , u.LastUsage , i.DataCompression , f.Fragmentation , IndexStats = STATS_DATE(i.ObjectID, i.IndexID) , IsLobLegacy = ISNULL(lob.IsLobLegacy, 0) , IsLob = ISNULL(lob.IsLob, 0) , IsSparse = CAST(CASE WHEN p.ObjectID IS NULL THEN 0 ELSE 1 END AS BIT) , IsPartitioned = CAST(CASE WHEN dds.[data_space_id] IS NOT NULL THEN 1 ELSE 0 END AS BIT) , FileGroupName = fg.[name] , i.IsUnique , i.IsPK , i.FillFactorValue , i.IsFiltered , a.IndexColumns , a.IncludedColumns FROM #Indexes i JOIN sys.objects o WITH(NOLOCK) ON o.[object_id] = i.ObjectID JOIN sys.schemas s WITH(NOLOCK) ON s.[schema_id] = o.[schema_id] LEFT JOIN #AggColumns a ON a.ObjectID = i.ObjectID AND a.IndexID = i.IndexID LEFT JOIN #Sparse p ON p.ObjectID = i.ObjectID LEFT JOIN #Fragmentation f ON f.ObjectID = i.ObjectID AND f.IndexID = i.IndexID AND f.PartitionNumber = i.PartitionNumber LEFT JOIN ( SELECT ObjectID = [object_id] , IndexID = [index_id] , TotalWrites = NULLIF([user_updates], 0) , TotalReads = NULLIF([user_seeks] + [user_scans] + [user_lookups], 0) , TotalSeeks = NULLIF([user_seeks], 0) , TotalScans = NULLIF([user_scans], 0) , TotalLookups = NULLIF([user_lookups], 0) , LastUsage = ( SELECT MAX(dt) FROM ( VALUES ([last_user_seek]) , ([last_user_scan]) , ([last_user_lookup]) , ([last_user_update]) ) t(dt) ) FROM sys.dm_db_index_usage_stats WITH(NOLOCK) WHERE [database_id] = @DBID ) u ON i.ObjectID = u.ObjectID AND i.IndexID = u.IndexID LEFT JOIN #Lob lob ON lob.ObjectID = i.ObjectID AND lob.IndexID = i.IndexID LEFT JOIN sys.destination_data_spaces dds WITH(NOLOCK) ON i.DataSpaceID = dds.[partition_scheme_id] AND i.PartitionNumber = dds.[destination_id] JOIN sys.filegroups fg WITH(NOLOCK) ON ISNULL(dds.[data_space_id], i.DataSpaceID) = fg.[data_space_id] WHERE o.[type] IN ('V', 'U') AND ( f.Fragmentation >= @Fragmentation OR i.PagesCount > @PreDescribeSize OR i.IndexType IN (5, 6) ) 


كما ترون من الاستعلامات نفسها ، غالبًا ما يتم استخدام الجداول المؤقتة. يتم ذلك بحيث لا يكون هناك إعادة تجميع ، وفي حالة وجود مخطط كبير ، يمكن إنشاء الخطة بشكل متوازٍ عند إدراج البيانات ، لأن الإدراج مع متغيرات الجدول ممكن في دفق واحد فقط.

بعد تنفيذ البرنامج النصي أعلاه ، ستظهر نافذة بها جدول الفهرس:



هنا يمكنك أيضًا عرض معلومات مفصلة أخرى ، مثل:

  1. قاعدة بيانات
  2. عدد الأقسام
  3. تاريخ ووقت آخر مكالمة
  4. ضغط
  5. مجموعة ملفات

ور. د.
يمكن تخصيص الأعمدة نفسها:



في خلايا عمود Fix ، يمكنك اختيار الإجراء الذي سيتم تنفيذه أثناء التحسين. أيضًا ، عند اكتمال المسح ، يتم تحديد الإجراء الافتراضي بناءً على الإعدادات المحددة:



يجب عليك تحديد الفهارس المطلوبة للمعالجة.

باستخدام القائمة الرئيسية ، يمكنك حفظ البرنامج النصي (الزر نفسه يبدأ عملية تحسين الفهرس نفسه):



احفظ الجدول بتنسيقات مختلفة (يسمح لك الزر نفسه بفتح إعدادات مفصلة لتحليل المؤشرات وتحسينها):



أيضا ، يمكن تحديث المعلومات من خلال النقر على الزر الثالث على اليسار في القائمة الرئيسية بجانب العدسة المكبرة.

يسمح لك الزر ذو العدسة المكبرة بتحديد قاعدة البيانات المطلوبة للنظر فيها.

لا يوجد حاليا نظام مساعدة كامل. لذلك ، الضغط على "؟" سيؤدي ذلك ببساطة إلى ظهور نافذة مشروطة تحتوي على معلومات أساسية حول منتج البرنامج:



بالإضافة إلى كل ما سبق ، تحتوي القائمة الرئيسية على شريط بحث:



عند بدء عملية تحسين الفهرس:



في الجزء السفلي من النافذة أيضًا ، يمكنك رؤية سجل الإجراءات المنفذة:



في نافذة التحليل المفصل وتحسين الفهارس ، يمكنك تكوين خيارات أكثر دقة:



اقتراحات للتطبيق:

  1. إتاحة إمكانية تحديث الإحصائيات بشكل انتقائي ليس فقط للفهارس ولكن أيضًا بطرق مختلفة (التحديث الكامل أو جزئيًا)
  2. لا تجعل تحديد قاعدة البيانات ممكنًا فحسب ، بل أيضًا خوادم مختلفة (يكون ذلك مناسبًا جدًا عند وجود العديد من مثيلات MS SQL Server)
  3. لمزيد من المرونة في الاستخدام ، يُقترح التفاف الأوامر في المكتبات ، وإخراجها إلى أوامر PowerShell ، كما هو الحال ، على سبيل المثال ، هنا: dbatools.io/commands
  4. إتاحة حفظ وتغيير الإعدادات الشخصية لكل من التطبيق بالكامل ، وإذا لزم الأمر ، لكل مثيل لـ MS SQL Server وكل قاعدة بيانات
  5. من الفقرتين 2 و 4 يتبع الرغبة في إنشاء مجموعات على قواعد البيانات ومجموعات على مثيلات MS SQL Server ، والتي الإعدادات هي نفسها
  6. البحث عن مؤشرات مكررة (كاملة وغير كاملة ، والتي إما تختلف قليلاً أو تختلف فقط في الأعمدة المضمنة)
  7. منذ يستخدم SQLIndexManager فقط لـ MS SQL Server DBMS ، تحتاج إلى عكس ذلك في الاسم ، على سبيل المثال ، كما يلي: SQLIndexManager for MS SQL Server
  8. قم بإزالة جميع أجزاء التطبيق من واجهة المستخدم الرسومية إلى وحدات منفصلة وأعد كتابتها إلى .NET Core 2.1

في وقت كتابة هذا التقرير ، تم تطوير المادة 6 من الرغبات بنشاط وهناك دعم بالفعل في شكل بحث عن التكرارات الكاملة والمماثلة:



مصادر


Source: https://habr.com/ru/post/ar461277/


All Articles