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:
en lyk so:
Alle versoeke word in die volgende lêers gegenereer:
indeks
Query
QueryEngine
Bedienerinligting
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:
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:
Vervolgens sal die volgende navrae na die DBBS geloods word:
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:
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:
Jy kan ook ander gedetailleerde inligting hier vertoon, soos:
databasis
aantal afdelings
datum en tyd van laaste oproep
kompressie
lêergroep
en so aan.
Die sprekers self kan aangepas word:
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:
Jy moet die verlangde indekse vir verwerking kies.
Deur die hoofkieslys te gebruik, kan u die skrif stoor (dieselfde knoppie begin die indeksoptimeringsproses self):
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):
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:
Benewens alles wat hierbo beskryf word, het die hoofkieslys 'n soekbalk:
Wanneer die indeksoptimeringsproses begin word:
Jy kan ook 'n log van aksies wat uitgevoer is onderaan die venster sien:
In die gedetailleerde instellingsvenster vir indeksanalise en optimalisering, kan u meer subtiele opsies instel:
Versoeke vir die aansoek:
maak dit moontlik om statistieke selektief op te dateer, nie net vir indekse nie en ook op verskillende maniere (volledig of gedeeltelik opdateer)
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)
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:
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
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
soek vir duplikaat-indekse (volledig en onvolledig, wat óf effens verskil óf verskil net in die ingesluit kolomme)
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
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: