SQL索引管理器 - 用于SQL Server和Azure上的索引维护的免费GUI工具
我作为SQL Server DBA工作了8年多,管理和优化服务器的性能。在我的空闲时间,我想为宇宙和我的同事做一些有用的事情。这就是我们最终为SQL Server和Azure 提供免费索引维护工具的方法。
理念
每隔一段时间,人们在处理他们的优先事项时,可能就像一个手指式电池 - 一个激励充电只持续一闪,然后一切都消失了。直到最近,我在这一生活观察中也不例外。我经常被想法创造属于我自己的想法所困扰,但优先级从一个变为另一个并且没有完成任何事情。
DevArt开发用于开发和管理SQL Server,MySQL和Oracle数据库的软件,对我的动机和专业成长产生了很大的影响。
在他们来之前,我对创建自己的产品的具体细节知之甚少,但在此过程中,我获得了很多关于SQL Server内部结构的知识。一年多以来,我一直致力于优化产品线中的查询,逐渐开始了解市场上哪些功能比另一种功能更受欢迎。
在某个阶段,制作一个新的利基产品的想法出现在我面前,但由于某些情况,这个想法没有成功。那时,基本上我没有为公司内部的新项目找到足够的资源而不影响核心业务。
在一个崭新的地方工作,并试图自己创建一个项目让我不断妥协。制造一个拥有所有花里胡哨的大产品的最初想法很快就会停止并逐渐转变为一个不同的方向 - 将计划的功能分解为单独的迷你工具并相互独立地实现它们。
因此,SQL Index Manager诞生了,它是SQL Server和Azure的免费索引维护工具。主要想法是将RedGate和Devart公司的商业替代品作为基础,并尝试在我自己的项目中改进其功能。
履行
口头上说,一切听起来都很简单......只需观看几个激励视频,打开“Rocky Balboa”模式,开始制作一款很酷的产品。但让我们面对音乐,一切都不那么乐观,因为在使用系统表函数时存在许多陷阱,sys.dm_db_index_physical_stats
同时,它是唯一可以从中获取有关索引碎片的最新信息的地方。
从开发的最初几天起,就有很好的机会在标准方案中制造沉闷的方式,并复制已经调试过的竞争应用程序的逻辑,同时添加一些自组织。但在分析了元数据的查询后,我想做一些更优化的事情,由于大公司的官僚主义,它们永远不会出现在他们的产品中。
在分析RedGate SQL索引管理器(v1.1.9.1378 - 每个用户155美元)时,您可以看到应用程序使用一种非常简单的方法:使用第一个查询,我们获得用户表和视图的列表,然后第二个,我们返回所选数据库中所有索引的列表。
- SELECT objects.name AS tableOrViewName
- , objects.object_id AS tableOrViewId
- , schemas.name AS schemaName
- , CAST(ISNULL(lobs.NumLobs, 0) AS BIT) AS ContainsLobs
- , o.is_memory_optimized
- FROM sys.objects AS objects
- JOIN sys.schemas AS schemas ON schemas.schema_id = objects.schema_id
- LEFT JOIN (
- SELECT object_id
- , COUNT(*) AS NumLobs
- FROM sys.columns WITH (NOLOCK)
- WHERE system_type_id IN (34, 35, 99)
- OR max_length = -1
- GROUP BY object_id
- ) AS lobs ON objects.object_id = lobs.object_id
- LEFT JOIN sys.tables AS o ON o.object_id = objects.object_id
- WHERE objects.type = 'U'
- OR objects.type = 'V'
- SELECT i.object_id AS tableOrViewId
- , i.name AS indexName
- , i.index_id AS indexId
- , i.allow_page_locks AS allowPageLocks
- , p.partition_number AS partitionNumber
- , CAST((c.numPartitions - 1) AS BIT) AS belongsToPartitionedIndex
- FROM sys.indexes AS i
- JOIN sys.partitions AS p ON p.index_id = i.index_id
- AND p.object_id = i.object_id
- JOIN (
- SELECT COUNT(*) AS numPartitions
- , object_id
- , index_id
- FROM sys.partitions
- GROUP BY object_id
- , index_id
- ) AS c ON c.index_id = i.index_id
- AND c.object_id = i.object_id
- WHERE i.index_id > 0 -- ignore heaps
- AND i.is_disabled = 0
- AND i.is_hypothetical = 0
接下来,在while
每个索引分区的循环中,发送请求以确定其大小和碎片级别。在扫描结束时,客户端上会显示重量小于进入阈值的索引。
- EXEC sp_executesql N'
- SELECT index_id, avg_fragmentation_in_percent, page_count
- FROM sys.dm_db_index_physical_stats(@databaseId, @objectId, @indexId, @partitionNr, NULL)'
- , N'@databaseId int,@objectId int,@indexId int,@partitionNr int'
- , @databaseId = 7, @objectId = 2133582639, @indexId = 1, @partitionNr = 1
- EXEC sp_executesql N'
- SELECT index_id, avg_fragmentation_in_percent, page_count
- FROM sys.dm_db_index_physical_stats(@databaseId, @objectId, @indexId, @partitionNr, NULL)'
- , N'@databaseId int,@objectId int,@indexId int,@partitionNr int'
- , @databaseId = 7, @objectId = 2133582639, @indexId = 2, @partitionNr = 1
- EXEC sp_executesql N'
- SELECT index_id, avg_fragmentation_in_percent, page_count
- FROM sys.dm_db_index_physical_stats(@databaseId, @objectId, @indexId, @partitionNr, NULL)'
- , N'@databaseId int,@objectId int,@indexId int,@partitionNr int'
- , @databaseId = 7, @objectId = 2133582639, @indexId = 3, @partitionNr = 1
在分析此应用程序的逻辑时,您可能会发现各种缺点。例如,在发送请求之前,不会检查当前分区是否包含任何行以从扫描中排除空分区。
但是问题在另一个方面表现得更加尖锐 - 对服务器的请求数量大约等于来自的总行数sys.partitions
。鉴于真实数据库可以包含数万个分区,这种细微差别可能导致对服务器的大量类似请求。在数据库位于远程服务器上的情况下,由于每个请求的执行中的网络延迟增加,扫描时间将更长,即使是最简单的一个。
与RedGate不同,由DevArt开发的类似产品 - 用于SQL Server的dbForge索引管理器(v1.10.38 - 每用户99美元)在一个大型查询中接收信息,然后在客户端上显示所有内容:
- SELECT SCHEMA_NAME(o.[schema_id]) AS [schema_name]
- , o.name AS parent_name
- , o.[type] AS parent_type
- , i.name
- , i.type_desc
- , s.avg_fragmentation_in_percent
- , s.page_count
- , p.partition_number
- , p.[rows]
- , ISNULL(lob.is_lob_legacy, 0) AS is_lob_legacy
- , ISNULL(lob.is_lob, 0) AS is_lob
- , CASE WHEN ds.[type] = 'PS' THEN 1 ELSE 0 END AS is_partitioned
- FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, NULL) s
- JOIN sys.partitions p ON s.[object_id] = p.[object_id]
- AND s.index_id = p.index_id
- AND s.partition_number = p.partition_number
- JOIN sys.indexes i ON i.[object_id] = s.[object_id]
- AND i.index_id = s.index_id
- LEFT JOIN (
- SELECT c.[object_id]
- , index_id = ISNULL(i.index_id, 1)
- , is_lob_legacy = MAX(CASE WHEN c.system_type_id IN (34, 35, 99) THEN 1 END)
- , is_lob = MAX(CASE WHEN c.max_length = -1 THEN 1 END)
- FROM sys.columns c
- LEFT JOIN sys.index_columns i ON c.[object_id] = i.[object_id]
- AND c.column_id = i.column_id
- AND i.index_id > 0
- WHERE c.system_type_id IN (34, 35, 99)
- OR c.max_length = -1
- GROUP BY c.[object_id], i.index_id
- ) lob ON lob.[object_id] = i.[object_id]
- AND lob.index_id = i.index_id
- JOIN sys.objects o ON o.[object_id] = i.[object_id]
- JOIN sys.data_spaces ds ON i.data_space_id = ds.data_space_id
- WHERE i.[type] IN (1, 2)
- AND i.is_disabled = 0
- AND i.is_hypothetical = 0
- AND s.index_level = 0
- AND s.alloc_unit_type_desc = 'IN_ROW_DATA'
- AND o.[type] IN ('U', 'V')
消除了竞争产品中类似请求的面纱的主要问题,但是这种实现的缺点是没有额外的参数传递给sys.dm_db_index_physical_stats
可以限制对明显不必要的索引的扫描的函数。实际上,这会导致获取系统中所有索引的信息以及扫描阶段不必要的磁盘负载。
值得一提的是,从中获取的源码数据sys.dm_db_index_physical_stats
并未永久缓存在缓冲池中,因此在获取有关索引碎片的信息时最小化物理读取是我的应用程序开发过程中的优先任务之一。
经过多次实验,我设法将扫描分为两部分,将两种方法结合起来。最初,一个大型请求通过过滤那些未包含在过滤范围中的分区来预先确定分区的大小:
- 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
接下来,我们只获取包含数据的分区,以避免从空索引中进行不必要的读取。
- 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)
根据设置,仅获取用户想要分析的索引类型(支持堆,群集/非群集索引和列存储)。
- INSERT INTO #Indexes
- SELECT ObjectID = i.[object_id]
- , IndexID = i.index_id
- , IndexName = i.[name]
- , PagesCount = a.ReservedPages
- , UnusedPagesCount = a.ReservedPages - a.UsedPages
- , 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
之后,我们添加了一些魔法,并且......对于所有小的索引,我们通过重复调用sys.dm_db_index_physical_stats
具有所有参数的完整指示的函数来确定碎片的级别。
- 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)
接下来,我们通过过滤掉额外的数据将所有可能的信息返回给客户端:
- 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)
- )
之后,点请求确定大型索引的碎片级别。
- EXEC sp_executesql N'
- DECLARE @DBID INT = DB_ID()
- SELECT [avg_fragmentation_in_percent]
- FROM sys.dm_db_index_physical_stats(@DBID, @ObjectID, @IndexID, @PartitionNumber, ''LIMITED'')
- WHERE [index_level] = 0
- AND [alloc_unit_type_desc] = ''IN_ROW_DATA'''
- , N'@ObjectID int,@IndexID int,@PartitionNumber int'
- , @ObjectId = 1044198770, @IndexId = 1, @PartitionNumber = 1
- EXEC sp_executesql N'
- DECLARE @DBID INT = DB_ID()
- SELECT [avg_fragmentation_in_percent]
- FROM sys.dm_db_index_physical_stats(@DBID, @ObjectID, @IndexID, @PartitionNumber, ''LIMITED'')
- WHERE [index_level] = 0
- AND [alloc_unit_type_desc] = ''IN_ROW_DATA'''
- , N'@ObjectID int,@IndexID int,@PartitionNumber int'
- , @ObjectId = 1552724584, @IndexId = 0, @PartitionNumber = 1
由于这种方法,在生成请求时,我设法解决了竞争对手应用程序中遇到的扫描性能问题。这可能是它的终结,但在开发过程中,逐渐出现了各种新的想法,这使得扩大我的产品的应用范围成为可能。
最初,实现了对使用的支持WAIT_AT_LOW_PRIORITY
,然后可以使用DATA_COMPRESSION
和FILL_FACTOR
重建索引。
该应用程序已被“撒上”以前未计划的功能,如维护列存储:
- SELECT *
- FROM (
- SELECT IndexID = [index_id]
- , PartitionNumber = [partition_number]
- , PagesCount = SUM([size_in_bytes]) / 8192
- , UnusedPagesCount = ISNULL(SUM(CASE WHEN [state] = 1 _
- THEN [size_in_bytes] END), 0) / 8192
- , Fragmentation = CAST(ISNULL(SUM(CASE WHEN [state] = 1 _
- THEN [size_in_bytes] END), 0)
- * 100. / SUM([size_in_bytes]) AS FLOAT)
- FROM sys.fn_column_store_row_groups(@ObjectID)
- GROUP BY [index_id]
- , [partition_number]
- ) t
- WHERE Fragmentation >= @Fragmentation
- AND PagesCount BETWEEN @MinIndexSize AND @MaxIndexSize
或者根据以下信息创建非聚簇索引的能力dm_db_missing_index
:
- SELECT ObjectID = d.[object_id]
- , UserImpact = gs.[avg_user_impact]
- , TotalReads = gs.[user_seeks] + gs.[user_scans]
- , TotalSeeks = gs.[user_seeks]
- , TotalScans = gs.[user_scans]
- , LastUsage = ISNULL(gs.[last_user_scan], gs.[last_user_seek])
- , IndexColumns =
- CASE
- WHEN d.[equality_columns] IS NOT NULL
- _AND d.[inequality_columns] IS NOT NULL
- THEN d.[equality_columns] + ', ' + d.[inequality_columns]
- WHEN d.[equality_columns] IS NOT NULL AND d.[inequality_columns] IS NULL
- THEN d.[equality_columns]
- ELSE d.[inequality_columns]
- END
- , IncludedColumns = d.[included_columns]
- FROM sys.dm_db_missing_index_groups g WITH(NOLOCK)
- JOIN sys.dm_db_missing_index_group_stats gs WITH(NOLOCK) _
- ON gs.[group_handle] = g.[index_group_handle]
- JOIN sys.dm_db_missing_index_details d WITH(NOLOCK) _
- ON g.[index_handle] = d.[index_handle]
- WHERE d.[database_id] = DB_ID()
结果和计划
关键的是,开发计划并没有就此结束,因为我渴望进一步开发这个应用程序网站源码。下一步是添加查找重复(已完成)或未使用索引的功能,以及实现对在SQL Server中维护统计信息的完全支持。
现在市场上有很多付费解决方案。我想相信,由于自由定位,更优化的查询以及各种有用的gismos的可用性,这个产品肯定会在日常任务中变得有用。
SQL索引管理器 - 用于SQL Server和Azure上的索引维护的免费GUI工具的更多相关文章
- [转]SQL Server 2008 如何配置报表管理器
本文转自:https://docs.microsoft.com/zh-cn/previous-versions/sql/sql-server-2008/cc281384%28v%3dsql.100%2 ...
- MySQL 索引管理与执行计划
1.1 索引的介绍 索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息.如果想按特定职员的姓来查找他或她,则与在表中搜索所有的行相比,索引有助于更快地获取信息. ...
- 【转】使用SQL Tuning Advisor STA优化SQL
SQL优化器(SQL Tuning Advisor STA)是Oracle10g中推出的帮助DBA优化工具,它的特点是简单.智能,DBA值需要调用函数就可以给出一个性能很差的语句的优化结果.下面介绍一 ...
- 如何用 SQL Tuning Advisor (STA) 优化SQL语句
在Oracle10g之前,优化SQL是个比较费力的技术活,不停的分析执行计划,加hint,分析统计信息等等.在10g中,Oracle推出了自己的SQL优化辅助工具: SQL优化器(SQL Tuning ...
- JMeter学习(二十五)HTTP属性管理器HTTP Cookie Manager、HTTP Request Defaults
Test Plan的配置元件中有一些和HTTP属性相关的元件:HTTP Cache Manager.HTTP Authorization Manager.HTTP Cookie Manager.HTT ...
- 【jmeter】HTTP属性管理器HTTP Cookie Manager、HTTP Request Defaults
Test Plan的配置元件中有一些和HTTP属性相关的元件:HTTP Cache Manager.HTTP Authorization Manager.HTTP Cookie Manager.HTT ...
- HTTP属性管理器详解
1)HTTP Cache Manager 2)HTTP Cookie 管理器 3)HTTP 信息头管理器 4)HTTP 授权管理器 5)HTTP 请求默认值 为什么会有这些http属性的配置元件? ...
- MySQL索引管理
一.索引介绍 1.什么是索引 1.索引好比一本书的目录,它能让你更快的找到自己想要的内容. 2.让获取的数据更有目的性,从而提高数据库索引数据的性能. 2.索引类型介绍 1.BTREE:B+树索引 2 ...
- HTTP属性管理器 初探
1)HTTP Cache Manager 2)HTTP Cookie 管理器 3)HTTP 信息头管理器 4)HTTP 授权管理器 5)HTTP 请求默认值 为什么会有这些http属性的配置元件? ...
随机推荐
- ansible服务部署
1.ansible.cfg配置文件 [defaults] #inventory= /home/op/ansible/testing #sudo_user=root remote_port=9122 r ...
- apicloud触底加载的简单实现
直接上干货 api.addEventListener({ name: 'scrolltobottom', extra: { threshold: 0 } }, function(ret, err) { ...
- linux设备驱动程序-设备树(2)-device_node转换成platform_device
设备树处理之--device_node转换成platform_device 以下讨论基于linux4.14,arm平台 platform device 设备树的产生就是为了替代driver中过多的pl ...
- hydra使用,实例介绍
hydra 是一个网络帐号破解工具,支持多种协议.其作者是van Hauser,David Maciejak与其共同维护.hydra在所有支持GCC的平台能很好的编译,包括Linux,所有版本的BSD ...
- JVM 对象查询语言(OQL)[转载]
最近生产环境出现一个很奇怪的问题,测试环境无法重现,本地直连生产无法重现.于是用上 jmap + Java VisualVM 的 OQL (Object Query Language) 分析问题. 关 ...
- 2016 ACM-ICPC Asia Regional Dalian Online HDU 5875 Function(线段树)
题意 求区间l~r的a[l]%a[l+1]%--%a[r]的值 思路 因为取模的变化是很快的,所以线段树查找区间内第一个小于等于a[l]的数的位置,更新ans后继续查找即可. 注意查询满足某种条件的位 ...
- JDK8在接口中引入的default
default关键字介绍 default是在java8中引入的关键字,也可称为Virtual extension methods——虚拟扩展方法.是指,在接口内部包含了一些默认的方法实现(也就是接口中 ...
- Android开发环境搭建(个人环境非通用)
1.安装andorid studio 2.连接模拟器,AMD处理器为无法使用AVD manager ,所以连接第三方的Genymotion模拟器,设置中安装Genymotion插件,重启即可(Geny ...
- Centos7 安装mysql-8.0.18(rpm)
1.前言 当前MySQL最新版本:8.0.18 (听说比5.7快2倍)官方之前表示:MySQL 8.0 正式版 8.0.18 已发布,MySQL 8 要比 MySQL 5.7 快 2 倍,还带来了大量 ...
- 12-numpy笔记-莫烦基本操作2
代码 import numpy as np A = np.arange(3,15) print('-1-') print(A) print('-2-') print(A[3]) A = np.aran ...