بررسی ابزار رایگان SQLIndexManager

همانطور که می دانید، ایندکس ها نقش مهمی در یک DBMS ایفا می کنند و یک جستجوی سریع برای رکوردهای مورد نیاز فراهم می کنند. به همین دلیل بسیار مهم است که به موقع به آنها خدمات رسانی کنید. مطالب بسیار زیادی در مورد تجزیه و تحلیل و بهینه سازی نوشته شده است، از جمله در اینترنت. به عنوان مثال، این موضوع اخیراً در بررسی شده است این انتشارات.

راه حل های پولی و رایگان زیادی برای این کار وجود دارد. به عنوان مثال، یک آماده وجود دارد تصمیم، بر اساس یک روش بهینه سازی شاخص تطبیقی.

بعد، بیایید به ابزار رایگان نگاه کنیم SQLIndexManager، نویسنده آلن دنتون.

تفاوت فنی اصلی بین SQLIndexManager و تعدادی آنالوگ دیگر توسط خود نویسنده ارائه شده است اینجا и اینجا.

در این مقاله نگاهی خارجی به پروژه و قابلیت های عملیاتی این راه حل نرم افزاری خواهیم داشت.

بحث در مورد این ابزار اینجا.
با گذشت زمان، بیشتر نظرات و اشکالات اصلاح شد.

بنابراین، بیایید اکنون به خود ابزار SQLIndexManager برویم.

این برنامه با C# .NET Framework 4.5 در Visual Studio 2017 نوشته شده است و از DevExpress برای فرم ها استفاده می کند:

بررسی ابزار رایگان SQLIndexManager

و شبیه این است:

بررسی ابزار رایگان SQLIndexManager

تمام درخواست ها در فایل های زیر ایجاد می شوند:

  1. شاخص
  2. پرس و جو
  3. QueryEngine
  4. اطلاعات سرور

بررسی ابزار رایگان SQLIndexManager

هنگام اتصال به پایگاه داده و ارسال درخواست به DBMS، برنامه به صورت زیر امضا می شود:

ApplicationName=”SQLIndexManager”

هنگامی که برنامه را اجرا می کنید، یک پنجره مودال برای افزودن یک اتصال باز می شود:
بررسی ابزار رایگان SQLIndexManager

در اینجا، بارگیری فهرست کاملی از تمام نمونه‌های MS SQL Server قابل دسترسی از طریق شبکه‌های محلی هنوز کار نمی‌کند.

همچنین می توانید با استفاده از دکمه سمت چپ در منوی اصلی یک اتصال اضافه کنید:

بررسی ابزار رایگان SQLIndexManager

در مرحله بعد، پرس و جوهای زیر برای DBMS راه اندازی می شود:

  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 است:

بررسی ابزار رایگان SQLIndexManager

شایان ذکر است که اطلاعات گسترده بر اساس حقوق نشان داده می شود. اگر آنجا sysadmin، سپس می توانید داده ها را از نما انتخاب کنید sys.master_files. اگر چنین حقوقی وجود نداشته باشد، داده های کمتری به سادگی برگردانده می شود تا سرعت درخواست کاهش نیابد.

در اینجا باید پایگاه داده های مورد علاقه خود را انتخاب کنید و روی دکمه "OK" کلیک کنید.

در مرحله بعد، اسکریپت زیر برای هر پایگاه داده انتخاب شده برای تجزیه و تحلیل وضعیت ایندکس ها اجرا می شود:

تجزیه و تحلیل وضعیت شاخص

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)
)

همانطور که از خود کوئری ها مشخص است، جداول موقت اغلب استفاده می شوند. این کار به گونه ای انجام می شود که هیچ کامپایل مجددی وجود نداشته باشد و در مورد یک طرح بزرگ، هنگام درج داده ها می توان طرح را به صورت موازی تولید کرد، زیرا درج متغیرهای جدول فقط در یک رشته امکان پذیر است.

پس از اجرای اسکریپت فوق، پنجره ای با جدول نمایه ظاهر می شود:

بررسی ابزار رایگان SQLIndexManager

همچنین می توانید اطلاعات دقیق دیگری مانند:

  1. پایگاه داده
  2. تعداد بخش ها
  3. تاریخ و زمان آخرین تماس
  4. فشار دادن
  5. گروه فایل

و t د.
خود بلندگوها را می توان سفارشی کرد:

بررسی ابزار رایگان SQLIndexManager

در سلول های ستون Fix می توانید انتخاب کنید که در حین بهینه سازی چه عملی انجام شود. همچنین، هنگامی که اسکن کامل می شود، یک عمل پیش فرض بر اساس تنظیمات انتخاب شده انتخاب می شود:

بررسی ابزار رایگان SQLIndexManager

شما باید شاخص های مورد نظر را برای پردازش انتخاب کنید.

با استفاده از منوی اصلی، می توانید اسکریپت را ذخیره کنید (همان دکمه فرآیند بهینه سازی فهرست را خود شروع می کند):

بررسی ابزار رایگان SQLIndexManager

و جدول را در قالب های مختلف ذخیره کنید (همان دکمه به شما امکان می دهد تنظیمات دقیق را برای تجزیه و تحلیل و بهینه سازی شاخص ها باز کنید):

بررسی ابزار رایگان SQLIndexManager

همچنین می توانید با کلیک بر روی دکمه سوم در سمت چپ در منوی اصلی کنار ذره بین اطلاعات را به روز کنید.

دکمه با ذره بین به شما امکان می دهد پایگاه داده های مورد نظر را برای بررسی انتخاب کنید.

در حال حاضر هیچ سیستم کمک جامعی وجود ندارد. بنابراین، دکمه "؟" را فشار دهید به سادگی باعث می شود یک پنجره مودال حاوی اطلاعات اولیه در مورد محصول نرم افزار ظاهر شود:

بررسی ابزار رایگان SQLIndexManager

علاوه بر همه چیزهایی که در بالا توضیح داده شد، منوی اصلی دارای یک نوار جستجو است:

بررسی ابزار رایگان SQLIndexManager

هنگام شروع فرآیند بهینه سازی شاخص:

بررسی ابزار رایگان SQLIndexManager

همچنین می توانید گزارشی از اقدامات انجام شده را در پایین پنجره مشاهده کنید:

بررسی ابزار رایگان SQLIndexManager

در پنجره تنظیمات دقیق برای تجزیه و تحلیل و بهینه سازی فهرست، می توانید گزینه های ظریف تری را پیکربندی کنید:

بررسی ابزار رایگان SQLIndexManager

درخواست برای درخواست:

  1. امکان به روز رسانی انتخابی آمار را نه تنها برای شاخص ها و همچنین به روش های مختلف (به طور کامل یا جزئی) فراهم می کند.
  2. نه تنها انتخاب یک پایگاه داده، بلکه سرورهای مختلف را نیز ممکن می کند (این بسیار راحت است زمانی که نمونه های زیادی از MS SQL Server وجود دارد)
  3. برای انعطاف بیشتر در استفاده، پیشنهاد می شود دستورات را در کتابخانه ها بپیچید و آنها را به دستورات PowerShell خروجی دهید، همانطور که برای مثال در اینجا انجام می شود:
  4. dbatools.io/commands
  5. ذخیره و تغییر تنظیمات شخصی برای کل برنامه و در صورت لزوم برای هر نمونه از MS SQL Server و هر پایگاه داده امکان پذیر است.
  6. از نقاط 2 و 4 نتیجه می شود که می خواهید گروه ها را بر اساس پایگاه داده و گروه هایی را با نمونه های MS SQL Server ایجاد کنید که تنظیمات برای آنها یکسان است.
  7. جستجو برای نمایه های تکراری (کامل و ناقص، که یا کمی متفاوت هستند یا فقط در ستون های موجود متفاوت هستند)
  8. از آنجایی که SQLIndexManager فقط برای MS SQL Server DBMS استفاده می شود، لازم است این را در نام، به عنوان مثال، به صورت زیر منعکس کنید: SQLIndexManager برای MS SQL Server
  9. تمام قسمت های غیر GUI برنامه را به ماژول های جداگانه منتقل کنید و آنها را در NET Core 2.1 بازنویسی کنید.

در زمان نگارش، مورد 6 از آرزوها به طور فعال در حال توسعه است و در حال حاضر پشتیبانی در قالب جستجوی موارد تکراری کامل و مشابه وجود دارد:

بررسی ابزار رایگان SQLIndexManager

منابع

منبع: www.habr.com

اضافه کردن نظر