目录

介绍

理念

实现

结果和计划


介绍

我作为SQL Server DBA工作了8年多,管理和优化服务器的性能。在我的空闲时间,我想为世界和我的同事做一些有用的事情。这就是我们最终为SQL Server和Azure 提供免费索引维护工具的原因。

理念

每隔一段时间,人们在处理他们的优先事项时,会像一个手指型电池——一个激励性的充电只持续一瞬间,然后一切都消失了。直到最近,我也不例外。我经常被自己的想法所困扰,但从一个到另一个的优先顺序改变了,什么都没有完成。

DevArt开发用于开发和管理SQL Server,MySQL和Oracle数据库的软件,对我的动机和专业成长产生了很大的影响。

在他们来之前,我对创建自己的产品的具体细节知之甚少,但在此过程中,我获得了很多关于SQL Server内部结构的知识。一年多以来,我一直致力于优化产品线中的查询,逐渐开始了解市场上哪些功能比另一种功能更受欢迎。

在某个阶段,制作一个新的利基产品的想法出现在我面前,但由于某些情况,这个想法没有成功。那时,基本上我没有为公司内部的新项目找到足够的资源而不影响核心业务。

在一个崭新的地方工作,并试图自己创建一个项目让我不断妥协。最初的想法是制造一个拥有所有功能的大产品,很快就停止了,并逐渐转变为一个不同的方向——将计划中的功能分解为单独的迷你工具,并彼此独立地实现它们。

因此,SQL索引管理器 诞生了,它是SQL Server和Azure的免费索引维护工具。主要想法是将RedGate和Devart公司的商业替代品作为基础,并尝试在我自己的项目中改进其功能。

实现

口头上说,一切听起来都很简单......只需观看几个激励视频,打开“Rocky Balboa”模式,开始制作一款很酷的产品。但让我们面对现实吧,一切都不那么乐观,因为在使用系统表函数sys.dm_db_index_physical_stats时存在许多陷阱,同时,它是唯一可以从中获取有关索引碎片的最新信息的地方。

从开发的最初几天起,就有很好的机会在标准方案中制造沉闷的方式,并复制已经调试过的竞争应用程序的逻辑,同时添加一些广告libbing。但在分析了元数据的查询后,我想做一些更优化的事情,由于大公司的官僚主义,它们永远不会出现在他们的产品中。

在分析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 NumLobsFROM sys.columns WITH (NOLOCK)WHERE system_type_id IN (34, 35, 99)OR max_length = -1GROUP 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_idAND p.object_id = i.object_id
JOIN (SELECT COUNT(*) AS numPartitions, object_id, index_idFROM sys.partitionsGROUP BY object_id, index_id
) AS c ON c.index_id = i.index_idAND c.object_id = i.object_id
WHERE i.index_id > 0 -- ignore heapsAND i.is_disabled = 0AND 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 = 1EXEC 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 = 1EXEC 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_idAND 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 cLEFT JOIN sys.index_columns i ON c.[object_id] = i.[object_id]AND c.column_id = i.column_idAND i.index_id > 0WHERE c.system_type_id IN (34, 35, 99)OR c.max_length = -1GROUP 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 = 0AND i.is_hypothetical = 0AND s.index_level = 0AND 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] > 255AND [rows] > 0AND [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 <= @PreDescribeSizeAND r.[index_level] = 0AND 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.ObjectIDAND a.IndexID = i.IndexID
LEFT JOIN #Sparse p ON p.ObjectID = i.ObjectID
LEFT JOIN #Fragmentation f ON f.ObjectID = i.ObjectIDAND f.IndexID = i.IndexIDAND 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.ObjectIDAND i.IndexID = u.IndexID
LEFT JOIN #Lob lob ON lob.ObjectID = i.ObjectIDAND 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 >= @FragmentationORi.PagesCount > @PreDescribeSizeORi.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] = 0AND [alloc_unit_type_desc] = ''IN_ROW_DATA''', N'@ObjectID int,@IndexID int,@PartitionNumber int', @ObjectId = 1044198770, @IndexId = 1, @PartitionNumber = 1EXEC 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] = 0AND [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 >= @FragmentationAND 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 =CASEWHEN d.[equality_columns] IS NOT NULL _AND d.[inequality_columns] IS NOT NULLTHEN d.[equality_columns] + ', ' + d.[inequality_columns]WHEN d.[equality_columns] IS NOT NULL AND d.[inequality_columns] IS NULLTHEN 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,这个产品在日常任务中肯定会变得有用。

可以在GitHub下载该应用程序的最新版本。源代码来源在同一个地方。

原文地址:https://www.codeproject.com/Articles/5162340/SQL-Index-Manager-Free-GUI-Tool-for-Index-Maintena

SQL索引管理器——用于SQL Server和Azure上的索引维护的免费GUI工具相关推荐

  1. SQL索引管理器——用于SQL Server和Azure上的索引维护的免费G​​UI工具

    目录 介绍 主意 实现 结果和计划 下载源 - 16.3 MB 下载 SQL 索引管理器 v1.0.0.68.zip - 16.2 MB 下载 SQL 索引管理器 v1.0.0.67.zip - 16 ...

  2. MySQL图形化管理器——EMS SQL Management Studio

    MySQL图形化管理器--EMS SQL Management Studio 在开发的时候,是否觉得MySQL实在让人觉得不方便,一行行的敲命令~ 今天推荐一个 MySQL图形化管理器--EMS SQ ...

  3. 如何将SolidWork许可管理器用于Draftsight Enterprise?

    如何在SolidNetwork许可管理器(SNLM)中添加Draftsight序列密钥? 1.在Windows打开"添加和删除程序" 2.搜索SolidNetwork许可管理器,然 ...

  4. 【问】启动SQL服务管理器时提示SQL Server could not find

    [问题描述] SQL Server could not find the default instance(MSSQLSEVER) [答] 该报错主要为SQL安装时实例没有安装成功,卸载SQL重新进行 ...

  5. sql服务找不到服务器,我找不到SQL服务管理器

    你的数据库安装成功?试试用sa登陆了吗?成功吗?用本地的能登陆上吗?如果用数据可以登陆.那么用这种方法连接数据库吧!1 新建一个.txt文件 2 改名:aa.udl 或 aa.UDL .后缀名为 ud ...

  6. sql加上唯一索引后批量插入_MySQL批量插入遇上唯一索引避免方法

    一.背景 以前使用SQL Server进行表分区的时候就碰到很多关于唯一索引的问题:Step8:SQL Server 当表分区遇上唯一约束,没想到在MySQL的分区中一样会遇到这样的问题:MySQL表 ...

  7. oracle的索引使用方法,在OracleE数据库的字段上建立索引的方法

    当where子句对某一列使用函数时,除非利用这个简单的技术强制索引,否则Oracle优化器不能在查询中使用索引. 通常情况下,如果在WHERE子句中不使用诸如UPPER.REPLACE 或SUBSTR ...

  8. Sql Server查询性能优化之索引篇【推荐】

    Sql Server查询性能优化之索引篇[推荐] 这篇是索引系列中比较完整的,经过整理而来的 一 索引基础知识 索引概述 1.概念 可以把索引理解为一种特殊的目录.就好比<新华字典>为了加 ...

  9. sql server 创建唯一性非聚集索引语句_数据库专题—索引原理

    深入浅出数据库索引原理 参见:https://www.cnblogs.com/aspwebchh/p/6652855.html 1.为什么给表加上主键? 1.平时创建表的时候,都会给表加上主键.如果没 ...

最新文章

  1. 四个使用this的典型应用
  2. java html api 百度云,Javase-6.0_中文API_HTML(最新更新)
  3. 【pyqt5学习】——最新版:配置external tools(designer、pyuic、pqrcc)
  4. wordpress多站点主站调用分站最新文章_企业网站SEO最新的7个优化步骤!
  5. 【Zookeeper】源码分析之Watcher机制(一)
  6. 错误码 0x8007007b 解决
  7. i5 10400f和r7 2700x选哪个?
  8. 电脑蓝牙耳机无法调节用关闭绝对音量来解决
  9. 使用int存储ip地址
  10. Raspberry Pi 3 -- Respeaker 4-mic的基本使用
  11. 几种intel CPU性能对比
  12. 大屏地图背景透明效果,高德地图怎么才能只渲染局部地图,有全部代码详细。
  13. 为了让师妹20分钟学会canvas,我熬夜苦肝本文外加一个小项目【❤️建议收藏❤️】
  14. 计算机毕业设计Java抑郁症患者博客交流平台(系统+源码+mysql数据库+Lw文档)
  15. 9.15蚂蚁金服hr面
  16. 使用python-requests爬虫模拟登陆中国海洋大学教务处网站
  17. 怎么用计算机算20次方,一个数的几次方怎么算,有简便方法吗?比如2的20次方,怎么算快?...
  18. Ubuntu_在Ubuntu 12.04 中安装iTunes
  19. 【无标题】积跬步,以致千里;积小流,以成江海。
  20. thinkpad t460p重量

热门文章

  1. c语言编译器储存有什么用,C编译器怎么样对内存划分和使用
  2. 信息科学 计算机 区别,电子信息科学技术和计算机科学技术有什么区别啊
  3. UI设计灵感|注册登录界面设计灵感
  4. 设计师喜欢收集各种各样的笔刷来喽,不必倾家倾产就能像创造出令人惊叹的设计。
  5. 准备一些万圣节的乐趣——UI设计素材模板
  6. QTableWidget item设置不可以选中和编辑状态
  7. 创建一个SQL测试数据库 - 消费者数据库
  8. python读取文件夹下所有文件的名字_一键汇总同一文件夹下所有表格,三种小技巧分享给你...
  9. Python List:一文彻底粉碎列表
  10. animation 先执行一次 在持续执行_FANUC机器人:先执行指令功能/后执行指令功能介绍与使用方法...