Hersiening van die gratis hulpmiddel SQLIndexManager

Soos u weet, speel indekse 'n belangrike rol in 'n DBBS, wat 'n vinnige soektog na die vereiste rekords bied. Daarom is dit so belangrik om hulle betyds te diens. Heelwat materiaal is oor ontleding en optimalisering geskryf, ook op die internet. Byvoorbeeld, hierdie onderwerp is onlangs hersien in hierdie publikasie.

Daar is baie betaalde en gratis oplossings hiervoor. Daar is byvoorbeeld 'n klaargemaakte besluit, gebaseer op 'n aanpasbare indeksoptimeringsmetode.

Kom ons kyk dan na die gratis nut SQLIndexManager, geskryf deur AlanDenton.

Die belangrikste tegniese verskil tussen SQLIndexManager en 'n aantal ander analoë word deur die skrywer self gegee hier и hier.

In hierdie artikel kyk ons ​​na buite na die projek en die operasionele vermoëns van hierdie sagteware-oplossing.

Bespreek hierdie nut hier.
Met verloop van tyd is meeste van die opmerkings en foute reggestel.

Dus, kom ons gaan nou aan na die SQLIndexManager-hulpprogram self.

Die toepassing is geskryf in C# .NET Framework 4.5 in Visual Studio 2017 en gebruik DevExpress vir vorms:

Hersiening van die gratis hulpmiddel SQLIndexManager

en lyk so:

Hersiening van die gratis hulpmiddel SQLIndexManager

Alle versoeke word in die volgende lêers gegenereer:

  1. indeks
  2. Query
  3. QueryEngine
  4. Bedienerinligting

Hersiening van die gratis hulpmiddel SQLIndexManager

Wanneer u aan 'n databasis koppel en navrae na die DBBS stuur, word die toepassing soos volg onderteken:

ApplicationName=”SQLIndexManager”

Wanneer jy die toepassing begin, sal 'n modale venster oopmaak om 'n verbinding by te voeg:
Hersiening van die gratis hulpmiddel SQLIndexManager

Hier werk die laai van 'n volledige lys van alle MS SQL Server-gevalle wat toeganklik is oor plaaslike netwerke nog nie.

U kan ook 'n verbinding byvoeg deur die knoppie heel links op die hoofkieslys te gebruik:

Hersiening van die gratis hulpmiddel SQLIndexManager

Vervolgens sal die volgende navrae na die DBBS geloods word:

  1. Verkry inligting oor die DBBS
    SELECT ProductLevel  = SERVERPROPERTY('ProductLevel')
         , Edition       = SERVERPROPERTY('Edition')
         , ServerVersion = SERVERPROPERTY('ProductVersion')
         , IsSysAdmin    = CAST(IS_SRVROLEMEMBER('sysadmin') AS BIT)
    

  2. Kry 'n lys van beskikbare databasisse met hul kort eienskappe
    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
    

Nadat die bogenoemde skrifte uitgevoer is, sal 'n venster verskyn wat kort inligting bevat oor die databasisse van die geselekteerde instansie van MS SQL Server:

Hersiening van die gratis hulpmiddel SQLIndexManager

Dit is opmerklik dat uitgebreide inligting op grond van regte gewys word. As daar stelseladministreerder, dan kan jy data uit die aansig kies sys.master_files. As daar nie sulke regte is nie, word minder data eenvoudig teruggestuur om nie die versoek te vertraag nie.

Hier moet u die databasisse van belang kies en op die "OK"-knoppie klik.

Vervolgens sal die volgende skrif vir elke geselekteerde databasis uitgevoer word om die toestand van die indekse te ontleed:

Indeks status analise

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

Soos uit die navrae self gesien kan word, word tydelike tabelle redelik gereeld gebruik. Dit word gedoen sodat daar geen hersamestelling is nie, en in die geval van 'n groot skema, kan die plan parallel gegenereer word wanneer data ingevoeg word, aangesien die invoeging van tabelveranderlikes slegs in een draad moontlik is.

Nadat die bogenoemde skrif uitgevoer is, sal 'n venster met 'n indekstabel verskyn:

Hersiening van die gratis hulpmiddel SQLIndexManager

Jy kan ook ander gedetailleerde inligting hier vertoon, soos:

  1. databasis
  2. aantal afdelings
  3. datum en tyd van laaste oproep
  4. kompressie
  5. lêergroep

en so aan.
Die sprekers self kan aangepas word:

Hersiening van die gratis hulpmiddel SQLIndexManager

In die selle van die Regmaak-kolom kan jy kies watter aksie tydens optimalisering uitgevoer sal word. Ook, wanneer die skandering voltooi is, word 'n verstekaksie gekies op grond van die geselekteerde instellings:

Hersiening van die gratis hulpmiddel SQLIndexManager

Jy moet die verlangde indekse vir verwerking kies.

Deur die hoofkieslys te gebruik, kan u die skrif stoor (dieselfde knoppie begin die indeksoptimeringsproses self):

Hersiening van die gratis hulpmiddel SQLIndexManager

en stoor die tabel in verskillende formate (dieselfde knoppie laat jou toe om gedetailleerde instellings oop te maak vir die ontleding en optimalisering van indekse):

Hersiening van die gratis hulpmiddel SQLIndexManager

Jy kan ook die inligting bywerk deur op die derde knoppie aan die linkerkant in die hoofkieslys langs die vergrootglas te klik.

Die knoppie met 'n vergrootglas laat jou toe om die verlangde databasisse vir oorweging te kies.

Daar is tans geen omvattende hulpstelsel nie. Druk dus die “?”-knoppie sal bloot 'n modale venster laat verskyn wat basiese inligting oor die sagtewareproduk bevat:

Hersiening van die gratis hulpmiddel SQLIndexManager

Benewens alles wat hierbo beskryf word, het die hoofkieslys 'n soekbalk:

Hersiening van die gratis hulpmiddel SQLIndexManager

Wanneer die indeksoptimeringsproses begin word:

Hersiening van die gratis hulpmiddel SQLIndexManager

Jy kan ook 'n log van aksies wat uitgevoer is onderaan die venster sien:

Hersiening van die gratis hulpmiddel SQLIndexManager

In die gedetailleerde instellingsvenster vir indeksanalise en optimalisering, kan u meer subtiele opsies instel:

Hersiening van die gratis hulpmiddel SQLIndexManager

Versoeke vir die aansoek:

  1. maak dit moontlik om statistieke selektief op te dateer, nie net vir indekse nie en ook op verskillende maniere (volledig of gedeeltelik opdateer)
  2. maak dit moontlik om nie net 'n databasis te kies nie, maar ook verskillende bedieners (dit is baie gerieflik wanneer daar baie gevalle van MS SQL Server is)
  3. Vir groter buigsaamheid in gebruik, word voorgestel om die opdragte in biblioteke toe te draai en dit na PowerShell-opdragte uit te voer, soos byvoorbeeld hier gedoen word:
  4. dbatools.io/commands
  5. maak dit moontlik om persoonlike instellings te stoor en te verander vir beide die hele toepassing en, indien nodig, vir elke instansie van MS SQL Server en elke databasis
  6. Uit punte 2 en 4 volg dit dat jy groepe wil skep volgens databasisse en groepe volgens MS SQL Server-instansies, waarvoor die instellings dieselfde is
  7. soek vir duplikaat-indekse (volledig en onvolledig, wat óf effens verskil óf verskil net in die ingesluit kolomme)
  8. Aangesien SQLIndexManager slegs vir MS SQL Server DBMS gebruik word, is dit nodig om dit in die naam, byvoorbeeld, soos volg te weerspieël: SQLIndexManager vir MS SQL Server
  9. Skuif alle nie-GUI-dele van die toepassing na aparte modules en herskryf dit in .NET Core 2.1

Met die skryf hiervan word item 6 van die wense aktief ontwikkel en daar is reeds ondersteuning in die vorm van soek na volledige en soortgelyke duplikate:

Hersiening van die gratis hulpmiddel SQLIndexManager

bronne

Bron: will.com

Voeg 'n opmerking