Semakan alat percuma SQLIndexManager

Seperti yang anda ketahui, indeks memainkan peranan penting dalam DBMS, menyediakan carian pantas kepada rekod yang diperlukan. Itulah sebabnya sangat penting untuk memberi perkhidmatan kepada mereka tepat pada masanya. Banyak bahan telah ditulis mengenai analisis dan pengoptimuman, termasuk di Internet. Sebagai contoh, topik ini telah disemak baru-baru ini dalam penerbitan ini.

Terdapat banyak penyelesaian berbayar dan percuma untuk ini. Sebagai contoh, ada yang siap keputusan, berdasarkan kaedah pengoptimuman indeks penyesuaian.

Seterusnya, mari lihat utiliti percuma SQLIndexManager, dikarang oleh AlanDenton.

Perbezaan teknikal utama antara SQLIndexManager dan beberapa analog lain diberikan oleh pengarang sendiri di sini ΠΈ di sini.

Dalam artikel ini, kami akan melihat di luar projek dan keupayaan operasi penyelesaian perisian ini.

Membincangkan utiliti ini di sini.
Lama kelamaan, kebanyakan komen dan pepijat telah diperbetulkan.

Jadi, mari kita beralih kepada utiliti SQLIndexManager itu sendiri.

Aplikasi ini ditulis dalam C# .NET Framework 4.5 dalam Visual Studio 2017 dan menggunakan DevExpress untuk borang:

Semakan alat percuma SQLIndexManager

dan kelihatan seperti ini:

Semakan alat percuma SQLIndexManager

Semua permintaan dijana dalam fail berikut:

  1. indeks
  2. Query
  3. Enjin Pertanyaan
  4. Maklumat Pelayan

Semakan alat percuma SQLIndexManager

Apabila menyambung ke pangkalan data dan menghantar pertanyaan ke DBMS, aplikasi ditandatangani seperti berikut:

ApplicationName=”SQLIndexManager”

Apabila anda melancarkan aplikasi, tetingkap modal akan dibuka untuk menambah sambungan:
Semakan alat percuma SQLIndexManager

Di sini, memuatkan senarai lengkap semua contoh MS SQL Server yang boleh diakses melalui rangkaian tempatan masih belum berfungsi.

Anda juga boleh menambah sambungan menggunakan butang paling kiri pada menu utama:

Semakan alat percuma SQLIndexManager

Seterusnya, pertanyaan berikut kepada DBMS akan dilancarkan:

  1. Mendapatkan maklumat tentang DBMS
    SELECT ProductLevel  = SERVERPROPERTY('ProductLevel')
         , Edition       = SERVERPROPERTY('Edition')
         , ServerVersion = SERVERPROPERTY('ProductVersion')
         , IsSysAdmin    = CAST(IS_SRVROLEMEMBER('sysadmin') AS BIT)
    

  2. Mendapatkan senarai pangkalan data yang tersedia dengan sifat ringkasnya
    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
    

Selepas melaksanakan skrip di atas, tetingkap akan muncul yang mengandungi maklumat ringkas tentang pangkalan data contoh pilihan MS SQL Server:

Semakan alat percuma SQLIndexManager

Perlu diingat bahawa maklumat lanjutan ditunjukkan berdasarkan hak. Jika ada pentadbir sistem, maka anda boleh memilih data daripada paparan sys.master_files. Sekiranya tidak ada hak sedemikian, maka kurang data yang dikembalikan supaya tidak melambatkan permintaan.

Di sini anda perlu memilih pangkalan data yang diminati dan klik pada butang "OK".

Seterusnya, skrip berikut akan dilaksanakan untuk setiap pangkalan data yang dipilih untuk menganalisis keadaan indeks:

Analisis status indeks

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

Seperti yang dapat dilihat daripada pertanyaan itu sendiri, jadual sementara digunakan agak kerap. Ini dilakukan supaya tidak ada penyusunan semula, dan dalam kes skema besar, pelan boleh dijana secara selari apabila memasukkan data, kerana memasukkan pembolehubah jadual hanya boleh dilakukan dalam satu utas.

Selepas melaksanakan skrip di atas, tetingkap dengan jadual indeks akan muncul:

Semakan alat percuma SQLIndexManager

Anda juga boleh memaparkan maklumat terperinci lain di sini, seperti:

  1. pangkalan data
  2. bilangan bahagian
  3. tarikh dan masa panggilan terakhir
  4. memerah
  5. kumpulan fail

dan t. d.
Pembesar suara itu sendiri boleh disesuaikan:

Semakan alat percuma SQLIndexManager

Dalam sel lajur Betulkan, anda boleh memilih tindakan yang akan dilakukan semasa pengoptimuman. Selain itu, apabila imbasan selesai, tindakan lalai dipilih berdasarkan tetapan yang dipilih:

Semakan alat percuma SQLIndexManager

Anda mesti memilih indeks yang dikehendaki untuk diproses.

Menggunakan menu utama, anda boleh menyimpan skrip (butang yang sama memulakan proses pengoptimuman indeks itu sendiri):

Semakan alat percuma SQLIndexManager

dan simpan jadual dalam format yang berbeza (butang yang sama membolehkan anda membuka tetapan terperinci untuk menganalisis dan mengoptimumkan indeks):

Semakan alat percuma SQLIndexManager

Anda juga boleh mengemas kini maklumat dengan mengklik pada butang ketiga di sebelah kiri dalam menu utama di sebelah kaca pembesar.

Butang dengan kaca pembesar membolehkan anda memilih pangkalan data yang dikehendaki untuk dipertimbangkan.

Pada masa ini tiada sistem bantuan yang komprehensif. Oleh itu, menekan butang β€œ?” hanya akan menyebabkan tetingkap modal muncul yang mengandungi maklumat asas tentang produk perisian:

Semakan alat percuma SQLIndexManager

Sebagai tambahan kepada semua yang diterangkan di atas, menu utama mempunyai bar carian:

Semakan alat percuma SQLIndexManager

Apabila memulakan proses pengoptimuman indeks:

Semakan alat percuma SQLIndexManager

Anda juga boleh melihat log tindakan yang dilakukan di bahagian bawah tetingkap:

Semakan alat percuma SQLIndexManager

Dalam tetingkap tetapan terperinci untuk analisis indeks dan pengoptimuman, anda boleh mengkonfigurasi pilihan yang lebih halus:

Semakan alat percuma SQLIndexManager

Permintaan untuk permohonan:

  1. memungkinkan untuk mengemas kini statistik secara selektif bukan sahaja untuk indeks dan juga dengan cara yang berbeza (kemas kini sepenuhnya atau sebahagiannya)
  2. memungkinkan bukan sahaja untuk memilih pangkalan data, tetapi juga pelayan yang berbeza (ini sangat mudah apabila terdapat banyak contoh MS SQL Server)
  3. Untuk fleksibiliti yang lebih besar dalam penggunaan, adalah dicadangkan untuk membungkus arahan dalam perpustakaan dan mengeluarkannya kepada arahan PowerShell, seperti yang dilakukan, sebagai contoh, di sini:
  4. dbatools.io/commands
  5. memungkinkan untuk menyimpan dan menukar tetapan peribadi untuk keseluruhan aplikasi dan, jika perlu, untuk setiap contoh MS SQL Server dan setiap pangkalan data
  6. Daripada mata 2 dan 4, anda ingin membuat kumpulan mengikut pangkalan data dan kumpulan mengikut contoh MS SQL Server, yang tetapan adalah sama
  7. cari indeks pendua (lengkap dan tidak lengkap, yang sama ada sedikit berbeza atau berbeza hanya dalam lajur yang disertakan)
  8. Memandangkan SQLIndexManager hanya digunakan untuk MS SQL Server DBMS, adalah perlu untuk mencerminkan ini dalam nama, sebagai contoh, seperti berikut: SQLIndexManager untuk MS SQL Server
  9. Alihkan semua bahagian bukan GUI aplikasi ke dalam modul berasingan dan tulis semula dalam .NET Core 2.1

Pada masa penulisan, item 6 hasrat sedang giat dibangunkan dan sudah ada sokongan dalam bentuk mencari pendua yang lengkap dan serupa:

Semakan alat percuma SQLIndexManager

sumber

Sumber: www.habr.com

Tambah komen