Kā zināms, indeksiem ir svarīga loma datubāzēs, nodrošinot ātru meklēšanu nepieciešamajās ierakstos. Tāpēc ir tik svarīgi tos savlaicīgi apkalpot. Par analīzi un optimizāciju ir rakstīts daudz materiālu, tostarp internetā. Piemēram, nesen tika veikts pārskats par šo tēmu .
Pastāv daudz maksas un bezmaksas risinājumu šim nolūkam. Piemēram, ir gatavs , kas balstās uz adaptīvo indeksu optimizācijas metodi.
Tālāk aplūkosim bezmaksas utilītu , kuras autors ir .
Galvenā tehniskā atšķirība starp SQLIndexManager un vairākām citām līdzīgām programmām ir autora paša sniegtais paskaidrojums. un .
Šajā rakstā tāpat aplūkosim projektu un programmatūras risinājuma izmantošanas iespējas.
Par šo utilītu diskutē .
Laika gaitā lielākā daļa piezīmju un kļūdu tika labotas.
Tāpēc tagad pievērsīsimies pašai utilītei SQLIndexManager.
Programma ir uzrakstīta C# .NET Framework 4.5 valodā, izmantojot Visual Studio 2017, un izmanto DevExpress veidlapām:
un izskatās šādi:
Visi vaicājumi tiek veidoti sekojošajos failos:
- Index
- Query
- QueryEngine
- ServerInfo
Pieslēdzoties datubāzei un nosūtot vaicājumus datubāzes pārvaldniekam, programma piesakās šādi:
ApplicationName=”SQLIndexManager” Palaidot programmu, parādīsies modālais logs savienojuma pievienošanai:
Šobrīd vēl nedarbojas pilna MS SQL Server instanču saraksta ielāde, kas pieejams lokālajos tīklos.
Savienojumu var pievienot arī, izmantojot ekrāna kreisajā malā esošo pogu galvenajā izvēlnē:
Tālāk tiks izpildīti sekojošie vaicājumi datubāzes pārvaldniekam:
- Informācijas saņemšana par datubāzes pārvaldnieku
SELECT ProductLevel = SERVERPROPERTY('ProductLevel') , Edition = SERVERPROPERTY('Edition') , ServerVersion = SERVERPROPERTY('ProductVersion') , IsSysAdmin = CAST(IS_SRVROLEMEMBER('sysadmin') AS BIT) - Pieejamo datubāzu saraksta iegūšana ar to īsiem īpašību aprakstiem
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
Pēc iepriekš minēto skriptu izpildes parādīsies logs, kurā būs īsa informācija par izvēlētā MS SQL Server instanču datubāzēm:
Jāatzīmē, ka paplašinātā informācija tiek parādīta atkarībā no tiesībām. Ja ir , tad var izvēlēties datus no skatījuma . Ja tādu tiesību nav, tad tiek atgriezti mazāk dati, lai nepalēninātu pieprasījumu.
Šeit nepieciešams izvēlēties interesējošās datu bāzes un nospiest pogu “Labi”.
Tālāk tiks izpildīts šis skripts katrai izvēlētajai datu bāzei indeksa stāvokļa analīzei:
Indeksa stāvokļa analīze
deklarēt @Fragmentation float=15;
deklarēt @MinIndexSize bigint=768;
deklarēt @MaxIndexSize bigint=1048576;
deklarēt @PreDescribeSize bigint=32768;
ISET NOCOUNT ON
ISET ARITHABORT ON
ISET NUMERIC_ROUNDABORT OFF
JA OBJECT_ID('tempdb.dbo.#AllocationUnits') NAV NULL
DROPS TABLE #AllocationUnits
IZVEIDOT TABLE #AllocationUnits (
ContainerID BIGINT PRIMARY KEY
, ReservedPages BIGINT NOT NULL
, UsedPages BIGINT NOT NULL
)
Ievietot #AllocationUnits (ContainerID, ReservedPages, UsedPages)
IZVĒLĒTIES [container_id]
, SUM([total_pages])
, SUM([used_pages])
NO sys.allocation_units AR WITH(NOLOCK)
GRUPA PA [container_id]
HAVING SUM([total_pages]) STARPU @MinIndexSize UN @MaxIndexSize
JA OBJECT_ID('tempdb.dbo.#ExcludeList') NAV NULL
DROPS TABLE #ExcludeList
IZVEIDOT TABLE #ExcludeList (ID INT PRIMARY KEY)
Ievietot #ExcludeList
IZVĒLĒTIES [object_id]
NO sys.objects AR WITH(NOLOCK)
KUR [type] I ('V', 'U')
UN ( [is_ms_shipped] = 1 )
JA OBJECT_ID('tempdb.dbo.#Partitions') NAV NULL
DROPS TABLE #Partitions
IZVĒLĒTIES [object_id]
, [index_id]
, [partition_id]
, [partition_number]
, [rows]
, [data_compression]
IEVADĪT #Partitions
NO sys.partitions AR WITH(NOLOCK)
KUR [object_id] > 255
UN [rows] > 0
UN [object_id] NAV (IZVĒLĒTIES * NO #ExcludeList)
JA OBJECT_ID('tempdb.dbo.#Indexes') NAV NULL
DROPS TABLE #Indexes
IZVEIDOT 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)
)
Ievietot #Indexes
IZVĒLĒTIES 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]
NO #AllocationUnits a
PIEVIENOT #Partitions p ON a.ContainerID = p.[partition_id]
PIEVIENOT sys.indexes i AR WITH(NOLOCK) ON i.[object_id] = p.[object_id] UN p.[index_id] = i.[index_id]
KUR i.[type] I (0, 1, 2, 5, 6)
UN i.[object_id] > 255
DEKLARĒT @files TABLE (ID INT PRIMARY KEY)
Ievietot @files
IZVĒLĒTIES DISTINCT [data_space_id]
NO sys.database_files AR WITH(NOLOCK)
KUR [state] != 0
UN [type] = 0
JA @@ROWCOUNT > 0 SĀKT
DZĒST NO i
NO #Indexes i
PA kreisi PAGALMOT sys.destination_data_spaces dds AR WITH(NOLOCK) ON i.DataSpaceID = dds.[partition_scheme_id] UN i.PartitionNumber = dds.[destination_id]
KUR ISNULL(dds.[data_space_id], i.DataSpaceID) I (IZVĒLĒTIES * NO @files)
BEIGĀ
DEKLARĒT @DBID INT
, @DBNAME SYSNAME
ISET @DBNAME = DB_NAME()
IZVĒLĒTIES @DBID = [database_id]
NO sys.databases AR WITH(NOLOCK)
KUR [name] = @DBNAME
JA OBJECT_ID('tempdb.dbo.#Fragmentation') NAV NULL
DROPS TABLE #Fragmentation
IZVEIDOT TABLE #Fragmentation (
ObjectID INT NOT NULL
, IndexID INT NOT NULL
, PartitionNumber INT NOT NULL
, Fragmentation FLOAT NOT NULL
, PRIMARY KEY (ObjectID, IndexID, PartitionNumber)
)
Ievietot #Fragmentation (ObjectID, IndexID, PartitionNumber, Fragmentation)
IZVĒLĒTIES i.ObjectID
, i.IndexID
, i.PartitionNumber
, r.[avg_fragmentation_in_percent]
NO #Indexes i
CROSS APPLY sys.dm_db_index_physical_stats(@DBID, i.ObjectID, i.IndexID, i.PartitionNumber, 'LIMITED') r
KUR i.PagesCount <= @PreDescribeSize
UN r.[index_level] = 0
UN r.[alloc_unit_type_desc] = 'IN_ROW_DATA'
UN i.IndexType I (0, 1, 2)
JA OBJECT_ID('tempdb.dbo.#Columns') NAV NULL
DROPS TABLE #Columns
IZVEIDOT 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)
)
Ievietot #Columns
IZVĒLĒTIES ObjectID = [object_id]
, ColumnID = [column_id]
, ColumnName = [name]
, SystemTypeID = [system_type_id]
, IsSparse = [is_sparse]
, IsColumnSet = [is_column_set]
, MaxLen = [max_length]
NO sys.columns AR WITH(NOLOCK)
KUR [object_id] I (IZVĒLĒTIES DISTINCT i.ObjectID NO #Indexes i)
JA OBJECT_ID('tempdb.dbo.#IndexColumns') NAV NULL
DROPS TABLE #IndexColumns
IZVEIDOT 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)
)
Ievietot #IndexColumns
IZVĒLĒTIES 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)
NO sys.index_columns ic AR WITH(NOLOCK)
KUR EXISITS(
IZVĒLĒTIES *
NO #Indexes i
KUR i.ObjectID = ic.[object_id]
UN i.IndexID = ic.[index_id]
UN i.IndexType I (1, 2)
)
JA OBJECT_ID('tempdb.dbo.#Lob') NAV NULL
DROPS TABLE #Lob
IZVEIDOT TABLE #Lob (
ObjectID INT NOT NULL
, IndexID INT NOT NULL
, IsLobLegacy BIT
, IsLob BIT
, PRIMARY KEY (ObjectID, IndexID)
)
Ievietot #Lob (ObjectID, IndexID, IsLobLegacy, IsLob)
IZVĒLĒTIES c.ObjectID
, IndexID = ISNULL(i.IndexID, 1)
, IsLobLegacy = MAX(CASE WHEN c.SystemTypeID I (34, 35, 99) THEN 1 END)
, IsLob = 0
NO #Columns c
PA kreisi PIEVIENOT #IndexColumns i ON c.ObjectID = i.ObjectID UN c.ColumnID = i.ColumnID
KUR c.SystemTypeID I (34, 35, 99)
GRUPA PA c.ObjectID
, i.IndexID
JA OBJECT_ID('tempdb.dbo.#Sparse') NAV NULL
DROPS TABLE #Sparse
IZVEIDOT TABLE #Sparse (ObjectID INT PRIMARY KEY)
Ievietot #Sparse
IZVĒLĒTIES DISTINCT ObjectID
NO #Columns
KUR IsSparse = 1
UN IsColumnSet = 1
JA OBJECT_ID('tempdb.dbo.#AggColumns') NAV NULL
DROPS TABLE #AggColumns
IZVEIDOT TABLE #AggColumns (
ObjectID INT NOT NULL
, IndexID INT NOT NULL
, IndexColumns NVARCHAR(MAX)
, IncludedColumns NVARCHAR(MAX)
, PRIMARY KEY (ObjectID, IndexID)
)
Ievietot #AggColumns
IZVĒLĒTIES t.ObjectID
, t.IndexID
, IndexColumns = STUFF((
IZVĒLĒTIES ', [' + c.ColumnName + ']'
NO #IndexColumns i
PA kreisi PIEVIENOT #Columns c ON i.ObjectID = c.ObjectID UN i.ColumnID = c.ColumnID
KUR i.ObjectID = t.ObjectID
UN i.IndexID = t.IndexID
UN i.IsIncluded = 0
GRUPA PA i.OrderID
)
FOR XML PATH(''), TYPE).value('(.\/text())[1]', 'NVARCHAR(MAX)'), 1, 2, '')
, IncludedColumns = STUFF((
IZVĒLĒTIES ', [' + c.ColumnName + ']'
NO #IndexColumns i
PA kreisi PIEVIENOT #Columns c ON i.ObjectID = c.ObjectID UN i.ColumnID = c.ColumnID
KUR i.ObjectID = t.ObjectID
UN i.IndexID = t.IndexID
UN i.IsIncluded = 1
GRUPA PA i.OrderID
)
FOR XML PATH(''), TYPE).value('(.\/text())[1]', 'NVARCHAR(MAX)'), 1, 2, '')
FROM (
IZVĒLĒTIES DISTINCT ObjectID, IndexID
NO #Indexes
KUR IndexType I (1, 2)
) t
IZVĒLĒTIES 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
NO #Indexes i
PIEVIENOT sys.objects o AR WITH(NOLOCK) ON o.[object_id] = i.ObjectID
PIEVIENOT sys.schemas s AR WITH(NOLOCK) ON s.[schema_id] = o.[schema_id]
PA kreisi PIEVIENOT #AggColumns a ON a.ObjectID = i.ObjectID UN a.IndexID = i.IndexID
PA kreisi PIEVIENOT #Sparse p ON p.ObjectID = i.ObjectID
PA kreisi PIEVIENOT #Fragmentation f ON f.ObjectID = i.ObjectID UN f.IndexID = i.IndexID UN f.PartitionNumber = i.PartitionNumber
PA kreisi PIEVIENOT (
IZVĒLĒTIES 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 = (
IZVĒLĒTIES MAX(dt)
NO (
VALUES ([last_user_seek])
, ([last_user_scan])
, ([last_user_lookup])
, ([last_user_update])
)
)
NO sys.dm_db_index_usage_stats AR WITH(NOLOCK)
NO [database_id] = @DBID
) u ON i.ObjectID = u.ObjectID UN i.IndexID = u.IndexID
PA kreisi PIEVIENOT #Lob lob ON lob.ObjectID = i.ObjectID UN lob.IndexID = i.IndexID
PA kreisi PIEVIENOT sys.destination_data_spaces dds AR WITH(NOLOCK) ON i.DataSpaceID = dds.[partition_scheme_id] UN i.PartitionNumber = dds.[destination_id]
PIEVIENOT sys.filegroups fg AR WITH(NOLOCK) ON ISNULL(dds.[data_space_id], i.DataSpaceID) = fg.[data_space_id]
KUR o.[type] I ('V', 'U')
UN (
f.Fragmentation >= @Fragmentation
UN
i.PagesCount > @PreDescribeSize
UN
i.IndexType I (5, 6)
)
Kā redzams no pašiem pieprasījumiem, diezgan bieži tiek izmantoti pagaidu tabulas. Tas tiek darīts, lai novērstu atkārtotas kompilācijas, un gadījumā, ja ir liela shēma, plāns var tikt ģenerēts paralēli, pievienojot datus, jo pievienošana ar tabulu mainīgajiem ir iespējama tikai vienā plūsmā.
Pēc iepriekš minētā skripta izpildes parādīsies logs ar indeksu tabulu:
Šeit var izvadīt arī citu detalizētu informāciju, piemēram:
- datubāze
- sekciju skaits
- pēdējā piekļuves datums un laiks
- saspiešana
- failu grupa
utt.
Pašas kolonnas var pielāgot:
Fix kolonnas šūnās var izvēlēties, kāda darbība tiks veikta optimizācijas laikā. Tāpat pēc skenēšanas pabeigšanas noklusējuma darbība tiek izvēlēta pamatojoties uz atlasītām iestatījumiem:
Nepieciešams izvēlēties nepieciešamos indeksus apstrādei.
Ar galveno izvēlni var gan saglabāt skriptu (šī pati poga uzsāk indeksu optimizācijas procesu):
tāpat var saglabāt tabulu dažādos formātos (šī pati poga ļauj atvērt detalizētus iestatījumus indeksu analīzei un optimizācijai):
Informāciju var arī atjaunināt, nospiežot trešo pogu no kreisās puses galvenajā izvēlnē blakus lupai.
Poga ar lupu ļauj izvēlēties nepieciešamās datubāzes apskatei.
Pilnīgas palīdzības sistēmas šobrīd nav. Tāpēc nospiežot pogu “?”, parādīsies tikai modālais logs, kas satur galveno informāciju par programmatūras produktu:
Papildus visam iepriekšminētajam galvenajā izvēlnē ir meklēšanas josla:
Sākot indeksu optimizācijas procesu:
Arī loga apakšdaļā var apskatīt izpildāmo darbību žurnālu:
Detalizēto iestatījumu logā par indeksu analīzi un optimizāciju var iestatīt smalkākas opcijas:
Ieteikumi lietojumprogrammai:
- padarīt iespējamu izvēlēties statistikas atjaunināšanu ne tikai indeksiem, bet arī dažādiem veidiem (pilnīgi atjaunināt vai daļēji)
- padarīt iespējamu izvēlēties ne tikai DB, bet arī dažādus serverus (tas ir ļoti ērti, kad ir daudz MS SQL Server instanču)
- lielākas elastības dēļ izmantošanā piedāvā iepakot komandas bibliotēkās un izvadīt PowerShell komandās, kā tas ir darīts, piemēram, šeit:
- ļaut saglabāt un mainīt personīgās iestatījums gan visai lietojumprogrammai, gan nepieciešamības gadījumā katram MS SQL Server instanca un katrai datubāzei
- no 2. un 4. punkta izriet vēlēšanās izveidot grupas pēc datubāzēm un grupas pēc MS SQL Server instancēm, kuru iestatījumi ir vienādi
- veikt meklēšanu dublikātu indeksos (pilnajos un nepilnajos, kas vai nu maz atšķiras, vai arī atšķiras tikai pēc iekļautajām kolonnām)
- tā kā SQLIndexManager tiek izmantots tikai MS SQL Server DB, tas ir jāatspoguļo nosaukumā, piemēram, šādā veidā: SQLIndexManager for MS SQL Server
- visas ne-GUI lietojumprogrammas daļas pārvietot uz atsevišķiem moduļiem un pārrakstīt .NET Core 2.1
Raksta rakstīšanas laikā 6. punkts no vēlēšanām tiek aktīvi attīstīts un jau ir izstrādāta atbalsts pilnu un līdzīgu dublikātu meklēšanai:
Avoti
Avots: habr.com
