Som vi vet spelar index en viktig roll i databashanteringssystem (DBMS) genom att möjliggöra snabb sökning av nödvÀndiga poster. DÀrför Àr det viktigt att underhÄlla dem i tid. Det har skrivits mycket om analys och optimering, bÄde i litteraturen och pÄ internet. Till exempel gjordes nyligen en översyn av detta Àmne i .
Det finns mÄnga bÄde betalda och gratis lösningar för detta. Till exempel finns det en fÀrdig , baserad pÄ en adaptiv metod för indexoptimering.
LÄt oss nu titta pÄ det kostnadsfria verktyget , av .
Huvudskillnaden i teknik mellan SQLIndexManager och andra liknande verktyg anges av sjÀlva författaren. och .
I denna artikel kommer vi att se pÄ projektet och dess möjligheter ur ett annat perspektiv.
Denna applikation diskuteras .
Under tiden har de flesta av kommentarerna och buggarna blivit ÄtgÀrdade.
LÄt oss nu gÄ vidare till sjÀlva verktyget SQLIndexManager.
Applikationen Àr skriven i C# .NET Framework 4.5 i Visual Studio 2017 och anvÀnder DevExpress för formulÀr:
och ser ut som följer:
Alla frÄgor genereras i följande filer:
- Index
- Query
- QueryEngine
- ServerInfo
Vid anslutning till databasen och skickande av frÄgor till DBMS kopplar applikationen upp sig med följande:
ApplicationName="SQLIndexManager" Vid start av applikationen öppnas ett modalt fönster för att lÀgga till en anslutning:
HÀr fungerar inte inladdningen av hela listan över alla MS SQL Server-exemplar som Àr tillgÀngliga över lokala nÀtverk Ànnu.
Man kan ocksÄ lÀgga till en anslutning med hjÀlp av den vÀnstra knappen i huvudmenyn:
DÀrefter kommer följande frÄgor att köras mot DBMS:
- HĂ€mta information om DBMS
SELECT ProductLevel = SERVERPROPERTY('ProductLevel') , Edition = SERVERPROPERTY('Edition') , ServerVersion = SERVERPROPERTY('ProductVersion') , IsSysAdmin = CAST(IS_SRVROLEMEMBER('sysadmin') AS BIT) - HÀmta lista över tillgÀngliga databaser med korta egenskaper
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
Efter att ovanstÄende skript har körts, kommer ett fönster att visas med kort information om databaserna i det valda MS SQL Server-exemplaret:
Det Àr vÀrt att notera att den utökade informationen visas baserat pÄ behörigheter. Om det finns , kan man vÀlja data frÄn vy . Om sÄdana rÀttigheter inte finns, returneras bara mindre data för att inte fördröja frÄgan.
HÀr mÄste man vÀlja de intressanta databaserna och klicka pÄ knappen "OK".
DÀrefter kommer följande skript att köras för varje vald databas för att analysera indexens tillstÄnd:
Analys av indexens tillstÄnd
deklarera @Fragmentation float=15;
deklarera @MinIndexSize bigint=768;
deklarera @MaxIndexSize bigint=1048576;
deklarera @PreDescribeSize bigint=32768;
SĂTT NOCOUNT PĂ
SĂTT ARITHABORT PĂ
SĂTT NUMERIC_ROUNDABORT AV
OM OBJECT_ID('tempdb.dbo.#AllocationUnits') ĂR INTE NULL
DROPA TABLE #AllocationUnits
SKAPA TABLE #AllocationUnits (
ContainerID BIGINT PRIMĂR NYCKEL
, ReservedPages BIGINT INTE NULL
, UsedPages BIGINT INTE NULL
)
INFĂR #AllocationUnits (ContainerID, ReservedPages, UsedPages)
VĂLJ [container_id]
, SUM([total_pages])
, SUM([used_pages])
FRĂ
N sys.allocation_units MED(NOLOCK)
GRUPPA EFTER [container_id]
HAVING SUM([total_pages]) MELLAN @MinIndexSize OCH @MaxIndexSize
OM OBJECT_ID('tempdb.dbo.#ExcludeList') ĂR INTE NULL
DROPA TABLE #ExcludeList
SKAPA TABLE #ExcludeList (ID INT PRIMĂR NYCKEL)
INFĂR #ExcludeList
VĂLJ [object_id]
FRĂ
N sys.objects MED(NOLOCK)
VAR [typ] IN ('V', 'U')
OCH ( [is_ms_shipped] = 1 )
OM OBJECT_ID('tempdb.dbo.#Partitions') ĂR INTE NULL
DROPA TABLE #Partitions
VĂLJ [object_id]
, [index_id]
, [partition_id]
, [partition_number]
, [rows]
, [data_compression]
INTO #Partitions
FRĂ
N sys.partitions MED(NOLOCK)
VAR [object_id] > 255
OCH [rows] > 0
OCH [object_id] IN (VĂLJ * FRĂ
N #ExcludeList)
OM OBJECT_ID('tempdb.dbo.#Indexes') ĂR INTE NULL
DROPA TABLE #Indexes
SKAPA TABLE #Indexes (
ObjectID INT INTE NULL
, IndexID INT INTE NULL
, IndexName SYSNAME NULL
, PagesCount BIGINT INTE NULL
, UnusedPagesCount BIGINT INTE NULL
, PartitionNumber INT INTE NULL
, RowsCount BIGINT INTE NULL
, IndexType TINYINT INTE NULL
, IsAllowPageLocks BIT INTE NULL
, DataSpaceID INT INTE NULL
, DataCompression TINYINT INTE NULL
, IsUnique BIT INTE NULL
, IsPK BIT INTE NULL
, FillFactorValue INT INTE NULL
, IsFiltered BIT INTE NULL
, PRIMĂR NYCKEL (ObjectID, IndexID, PartitionNumber)
)
INFĂR #Indexes
VĂLJ ObjectID = i.[object_id]
, IndexID = i.index_id
, IndexName = i.[name]
, PagesCount = a.ReservedPages
, UnusedPagesCount = FALL NĂR ABS(a.ReservedPages - a.UsedPages) > 32 DĂ
a.ReservedPages - a.UsedPages ANNARS 0 SLUT
, 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]
FRĂ
N #AllocationUnits a
GĂ
MED #Partitions p PĂ
a.ContainerID = p.[partition_id]
GĂ
MED sys.indexes i MED(NOLOCK) PĂ
i.[object_id] = p.[object_id] OCH p.[index_id] = i.[index_id]
VAR i.[type] IN (0, 1, 2, 5, 6)
OCH i.[object_id] > 255
DEKLARERA @files TABLE (ID INT PRIMĂR NYCKEL)
INFĂR @files
VĂLJ DISTINKT [data_space_id]
FRĂ
N sys.database_files MED(NOLOCK)
VAR [state] != 0
OCH [type] = 0
OM @@ROWCOUNT > 0 BĂRJA
RADERA FRĂ
N i
FRĂ
N #Indexes i
VĂNSTER GĂ
MED sys.destination_data_spaces dds MED(NOLOCK) PĂ
i.DataSpaceID = dds.[partition_scheme_id] OCH i.PartitionNumber = dds.[destination_id]
VAR ISNULL(dds.[data_space_id], i.DataSpaceID) IN (VĂLJ * FRĂ
N @files)
SLUT
DEKLARERA @DBID INT
, @DBNAME SYSNAME
SĂTT @DBNAME = DB_NAME()
VĂLJ @DBID = [database_id]
FRĂ
N sys.databases MED(NOLOCK)
VAR [name] = @DBNAME
OM OBJECT_ID('tempdb.dbo.#Fragmentation') ĂR INTE NULL
DROPA TABLE #Fragmentation
SKAPA TABLE #Fragmentation (
ObjectID INT INTE NULL
, IndexID INT INTE NULL
, PartitionNumber INT INTE NULL
, Fragmentation FLOAT INTE NULL
, PRIMĂR NYCKEL (ObjectID, IndexID, PartitionNumber)
)
INFĂR #Fragmentation (ObjectID, IndexID, PartitionNumber, Fragmentation)
VĂLJ i.ObjectID
, i.IndexID
, i.PartitionNumber
, r.[avg_fragmentation_in_percent]
FRĂ
N #Indexes i
CROSS APPLY sys.dm_db_index_physical_stats(@DBID, i.ObjectID, i.IndexID, i.PartitionNumber, 'LIMITED') r
VAR i.PagesCount = @Fragmentation
ELLER
i.PagesCount > @PreDescribeSize
ELLER
i.IndexType IN (5, 6)
)
Som det framgÄr av sjÀlva förfrÄgningarna anvÀnds tillfÀlliga tabeller ganska ofta. Detta görs för att undvika re-kompileringar, och i hÀndelse av stora scheman kan planen genereras parallellt vid datainsÀttning, eftersom insÀttning med tabellvariabler endast Àr möjlig i en process.
Efter att ha kört det ovanstÄende skriptet kommer ett fönster med en index-tabell att visas:
HÀr kan Àven annan detaljerad information visas, sÄsom:
- databas
- antal sektioner
- datum och tid för senaste ÄtgÀrd
- kompression
- filgrupp
och sÄ vidare.
SjÀlva kolumnerna kan anpassas:
I cellerna i kolumnen Fix kan du vÀlja vilken ÄtgÀrd som ska utföras vid optimering. Dessutom vÀljs standardÄtgÀrden efter avslutad skanning baserat pÄ valda instÀllningar:
Det Àr nödvÀndigt att vÀlja de index som ska bearbetas.
Med hjÀlp av huvudmenyn kan du bÄde spara skriptet (denna knapp startar ocksÄ sjÀlva process för indexoptimering):
eller spara tabellen i olika format (denna knapp ger ocksÄ möjlighet att öppna detaljerade instÀllningar för analys och optimering av index):
Informationen kan ocksÄ uppdateras genom att klicka pÄ den tredje knappen till vÀnster i huvudmenyn bredvid förstoringsglaset.
Knappen med förstoringsglaset lÄter dig vÀlja de databaser som ska granskas.
För nĂ€rvarande finns det inget fullstĂ€ndigt hjĂ€lp-system. DĂ€rför kommer ett klick pĂ„ knappen â?â bara att visa ett modalfönster som innehĂ„ller grundinformation om programprodukten:
Utöver allt ovanstÄende finns det en sökrad i huvudmenyn:
Vid start av indexoptimeringsprocessen:
Du kan ocksÄ se loggen över utförda ÄtgÀrder lÀngst ner i fönstret:
I fönstret för detaljerade instÀllningar av analys och optimering av index kan du justera mer fina alternativ:
ĂnskemĂ„l om applikationen:
- göra det möjligt att selektivt uppdatera statistik inte bara för index utan ocksÄ pÄ olika sÀtt (uppdatera helt eller delvis)
- göra det möjligt att vÀlja inte bara databaser utan ocksÄ olika servrar (detta Àr mycket praktiskt nÀr man har mÄnga instanser av MS SQL Server)
- för större flexibilitet i anvÀndningen föreslÄs det att kapsla in kommandon i bibliotek och presentera dem som PowerShell-kommandon, som det görs hÀr:
- möjliggöra att spara och Àndra personliga instÀllningar bÄde för hela applikationen och om det behövs för varje instans av MS SQL Server och varje databas
- frÄn punkterna 2 och 4 följer önskemÄlet att skapa grupper för databaser och grupper för instanser av MS SQL Server, för vilka instÀllningarna Àr desamma
- utföra en sökning efter dubbletter av index (hela och ofullstÀndiga, som antingen skiljer sig lite eller skiljer sig endast Ät avseende inkluderade kolumner)
- eftersom SQLIndexManager endast anvÀnds för databashanteringssystemet MS SQL Server, bör detta Äterspeglas i namnet, till exempel pÄ följande sÀtt: SQLIndexManager för MS SQL Server
- alla delar av applikationen som inte Àr GUI bör flyttas till separata moduler och skrivas om i .NET Core 2.1
Vid tidpunkten för skrivandet av artikeln arbetas punkt 6 i önskemÄlen aktivt med och det finns redan stöd i form av sökning efter hela och liknande dubbletter:
KĂ€llor
KĂ€lla: habr.com
