我作为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美元)时,您可以看到网站源码使用一种非常简单的方法:使用第一个查询,我们获得用户表和视图的列表,然后第二个,我们返回所选数据库中所有索引的列表。
<span style="color:#000000"><span style="color:#0000ff">SELECT</span> objects.name <span style="color:#0000ff">AS</span> tableOrViewName
, objects.object_id <span style="color:#0000ff">AS</span> tableOrViewId
, schemas.name <span style="color:#0000ff">AS</span> schemaName
, CAST(ISNULL(lobs.NumLobs, <span style="color:#000080">0</span>) <span style="color:#0000ff">AS</span> <span style="color:#0000ff">BIT</span>) <span style="color:#0000ff">AS</span> ContainsLobs
, o.is_memory_optimized
<span style="color:#0000ff">FROM</span> sys.objects <span style="color:#0000ff">AS</span> objects
<span style="color:#0000ff">JOIN</span> sys.schemas <span style="color:#0000ff">AS</span> schemas <span style="color:#0000ff">ON</span> schemas.schema_id = objects.schema_id
<span style="color:#0000ff">LEFT</span> <span style="color:#0000ff">JOIN</span> (
<span style="color:#0000ff">SELECT</span> object_id
, COUNT(*) <span style="color:#0000ff">AS</span> NumLobs
<span style="color:#0000ff">FROM</span> sys.columns <span style="color:#0000ff">WITH</span> (<span style="color:#0000ff">NOLOCK</span>)
<span style="color:#0000ff">WHERE</span> system_type_id <span style="color:#0000ff">IN</span> (<span style="color:#000080">34</span>, <span style="color:#000080">35</span>, <span style="color:#000080">99</span>)
<span style="color:#0000ff">OR</span> max_length = -1
<span style="color:#0000ff">GROUP</span> <span style="color:#0000ff">BY</span> object_id
) <span style="color:#0000ff">AS</span> lobs <span style="color:#0000ff">ON</span> objects.object_id = lobs.object_id
<span style="color:#0000ff">LEFT</span> <span style="color:#0000ff">JOIN</span> sys.tables <span style="color:#0000ff">AS</span> o <span style="color:#0000ff">ON</span> o.object_id = objects.object_id
<span style="color:#0000ff">WHERE</span> objects.<span style="color:#0000ff">type</span> = <span style="color:#800080">'</span><span style="color:#800080">U'</span>
<span style="color:#0000ff">OR</span> objects.<span style="color:#0000ff">type</span> = <span style="color:#800080">'</span><span style="color:#800080">V'</span>
<span style="color:#0000ff">SELECT</span> i.object_id <span style="color:#0000ff">AS</span> tableOrViewId
, i.name <span style="color:#0000ff">AS</span> indexName
, i.index_id <span style="color:#0000ff">AS</span> indexId
, i.allow_page_locks <span style="color:#0000ff">AS</span> allowPageLocks
, p.partition_number <span style="color:#0000ff">AS</span> partitionNumber
, CAST((c.numPartitions - <span style="color:#000080">1</span>) <span style="color:#0000ff">AS</span> <span style="color:#0000ff">BIT</span>) <span style="color:#0000ff">AS</span> belongsToPartitionedIndex
<span style="color:#0000ff">FROM</span> sys.indexes <span style="color:#0000ff">AS</span> i
<span style="color:#0000ff">JOIN</span> sys.partitions <span style="color:#0000ff">AS</span> p <span style="color:#0000ff">ON</span> p.index_id = i.index_id
<span style="color:#0000ff">AND</span> p.object_id = i.object_id
<span style="color:#0000ff">JOIN</span> (
<span style="color:#0000ff">SELECT</span> COUNT(*) <span style="color:#0000ff">AS</span> numPartitions
, object_id
, index_id
<span style="color:#0000ff">FROM</span> sys.partitions
<span style="color:#0000ff">GROUP</span> <span style="color:#0000ff">BY</span> object_id
, index_id
) <span style="color:#0000ff">AS</span> c <span style="color:#0000ff">ON</span> c.index_id = i.index_id
<span style="color:#0000ff">AND</span> c.object_id = i.object_id
<span style="color:#0000ff">WHERE</span> i.index_id > <span style="color:#000080">0</span> <em><span style="color:#008000">--</span></em><em><span style="color:#008000"> ignore heaps</span></em>
<span style="color:#0000ff">AND</span> i.is_disabled = <span style="color:#000080">0</span>
<span style="color:#0000ff">AND</span> i.is_hypothetical = <span style="color:#000080">0</span></span>
接下来,在while
每个索引分区的循环中,发送请求以确定其大小和碎片级别。在扫描结束时,客户端上会显示重量小于进入阈值的索引。
<span style="color:#000000"><span style="color:#0000ff">EXEC</span> sp_executesql N<span style="color:#800080">'</span><span style="color:#800080">
SELECT index_id, avg_fragmentation_in_percent, page_count
FROM sys.dm_db_index_physical_stats(@databaseId, @objectId, @indexId, @partitionNr, NUL