c# - 有什么方法可以通过包含实际执行计划以编程方式执行查询并查看是否有任何索引建议

标签 c# sql sql-server ssms sql-server-performance

我有很多查询,我想使用 sql server management studio 上的“包含实际执行计划”功能来测试每个查询

但是我不可能为 1m + 查询手动执行此操作

所以我想知道我是否可以使用包含实际执行计划功能以编程方式(从 c#)执行它们,并查看 SQL 服务器是否建议任何索引

enter image description here

最佳答案

首先,在我介绍如何在代码中获取实际执行计划并找到报告需要索引的计划之前,我建议您查看使用 Database Engine Tuning Adviser (DTA) ,你可以给它一个所有查询的列表,它会处理它们,告诉你可能的索引、统计数据和许多其他可以帮助你规划查询的东西。

比给它一个超过 100 万个查询的列表更好的是,您可以从服务器获取正在运行的实际查询的跟踪信息,并且它会专注于占用最多时间的查询。


要回答您的原始问题,您需要添加 SET STATISTICS XML ON在连接开始时,这将为您提供显示的 GUI 所基于的 XML 数据。 (See here for more info about getting the plans)。执行此操作后,您的查询将返回一个额外的结果集,其中包含第一列第一行中计划的 xml。

这是一个快速而肮脏的函数。

private static string GetXmlPlanForQuery(string queryText)
{
    string result = null;
    using (var connection = new SqlConnection(connectionString))
    using (var command = new SqlCommand())
    {
        connection.Open();
        command.Connection = connection;

        //Enable the statistics.
        command.CommandText = "SET STATISTICS XML ON";
        command.ExecuteNonQuery();

        //Run through the query, keeping the first row first column of the last result set.
        command.CommandText = queryText;
        using (var reader = command.ExecuteReader())
        {
            object lastValue = null;
            do
            {
                if (reader.Read())
                {
                    lastValue = reader.GetValue(0);
                }
            } while (reader.NextResult());

            if (lastValue != null)
            {
                result = lastValue as string;
            }
        }
    }
    return result;
}

这是它为查询 select TOTAL_SALES from clients where ACTIVE = 0; 返回的 XML我在我的一个本地数据库上运行的。

<?xml version="1.0"?>
<ShowPlanXML xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan" Version="1.2" Build="11.0.5058.0">
  <BatchSequence>
    <Batch>
      <Statements>
        <StmtSimple StatementText="SELECT [TOTAL_SALES] FROM [clients] WHERE [ACTIVE]=@1" StatementId="1" StatementCompId="1" StatementType="SELECT" RetrievedFromCache="false" StatementSubTreeCost="0.0767454" StatementEstRows="315" StatementOptmLevel="FULL" QueryHash="0x708AE72DD31A316" QueryPlanHash="0x214EA79FF76E6771" StatementOptmEarlyAbortReason="GoodEnoughPlanFound">
          <StatementSetOptions QUOTED_IDENTIFIER="true" ARITHABORT="false" CONCAT_NULL_YIELDS_NULL="true" ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" NUMERIC_ROUNDABORT="false"/>
          <QueryPlan DegreeOfParallelism="1" CachedPlanSize="16" CompileTime="1" CompileCPU="1" CompileMemory="192">
            <MissingIndexes>
              <MissingIndexGroup Impact="94.0522">
                <MissingIndex Database="[exampleDb]" Schema="[dbo]" Table="[CLIENTS]">
                  <ColumnGroup Usage="EQUALITY">
                    <Column Name="[ACTIVE]" ColumnId="15"/>
                  </ColumnGroup>
                  <ColumnGroup Usage="INCLUDE">
                    <Column Name="[TOTAL_SALES]" ColumnId="18"/>
                  </ColumnGroup>
                </MissingIndex>
              </MissingIndexGroup>
            </MissingIndexes>
            <MemoryGrantInfo SerialRequiredMemory="0" SerialDesiredMemory="0"/>
            <OptimizerHardwareDependentProperties EstimatedAvailableMemoryGrant="830838" EstimatedPagesCached="207709" EstimatedAvailableDegreeOfParallelism="2"/>
            <RelOp NodeId="0" PhysicalOp="Clustered Index Scan" LogicalOp="Clustered Index Scan" EstimateRows="315" EstimateIO="0.0749769" EstimateCPU="0.0017685" AvgRowSize="16" EstimatedTotalSubtreeCost="0.0767454" TableCardinality="1465" Parallel="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row">
              <OutputList>
                <ColumnReference Database="[exampleDb]" Schema="[dbo]" Table="[CLIENTS]" Column="TOTAL_SALES"/>
              </OutputList>
              <RunTimeInformation>
                <RunTimeCountersPerThread Thread="0" ActualRows="315" ActualEndOfScans="1" ActualExecutions="1"/>
              </RunTimeInformation>
              <IndexScan Ordered="0" ForcedIndex="0" ForceScan="0" NoExpandHint="0">
                <DefinedValues>
                  <DefinedValue>
                    <ColumnReference Database="[exampleDb]" Schema="[dbo]" Table="[CLIENTS]" Column="TOTAL_SALES"/>
                  </DefinedValue>
                </DefinedValues>
                <Object Database="[exampleDb]" Schema="[dbo]" Table="[CLIENTS]" Index="[imp_clpk_CLIENTS]" IndexKind="Clustered"/>
                <Predicate>
                  <ScalarOperator ScalarString="[exampleDb].[dbo].[CLIENTS].[ACTIVE]=(0)">
                    <Compare CompareOp="EQ">
                      <ScalarOperator>
                        <Identifier>
                          <ColumnReference Database="[exampleDb]" Schema="[dbo]" Table="[CLIENTS]" Column="ACTIVE"/>
                        </Identifier>
                      </ScalarOperator>
                      <ScalarOperator>
                        <Const ConstValue="(0)"/>
                      </ScalarOperator>
                    </Compare>
                  </ScalarOperator>
                </Predicate>
              </IndexScan>
            </RelOp>
            <ParameterList>
              <ColumnReference Column="@1" ParameterCompiledValue="(0)" ParameterRuntimeValue="(0)"/>
            </ParameterList>
          </QueryPlan>
        </StmtSimple>
      </Statements>
    </Batch>
  </BatchSequence>
</ShowPlanXML>

现在,因为微软非常好,如果你 navigate to the namespace listed in the XML您实际上可以获得 .xsd 的副本对于格式。然后您可以在开发人员的命令提示符下执行 xsd showplanxml.xsd /classes它会给你一个 showplanxml.cs您可以与 XmlSerializer 一起使用.

这是一个小示例程序,它在缺少索引时执行调试器中断。

static void Main(string[] args)
{
    string result = GetXmlPlanForQuery("select TOTAL_SALES from clients where ACTIVE = 0;");
    XmlSerializer ser = new XmlSerializer(typeof(ShowPlanXML));
    var plan = (ShowPlanXML)ser.Deserialize(new StringReader(result));

    var missingIndexes =
        plan.BatchSequence.SelectMany(x => x)
            .SelectMany(x => x.Items)
            .OfType<StmtSimpleType>()
            .Select(x => x.QueryPlan)
            .Where(x => x.MissingIndexes != null && x.MissingIndexes.Any());

    foreach (var queryPlan in missingIndexes)
    {
        //This will hit for each statement in the query that was missing a index, check queryPlan.MissingIndexes to see the indexes that are missing.
        Debugger.Break();
    }

    Console.WriteLine("Done");
    Console.ReadLine();
}

我使用 XmlSerializer 并将其反序列化为一个类,但您可以轻松地将其加载到 XDocument 中,然后使用 XPath找到所有名为 MissingIndex 的节点.

关于c# - 有什么方法可以通过包含实际执行计划以编程方式执行查询并查看是否有任何索引建议,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25879543/

相关文章:

c# - 在 PHP 中通过 COM 访问 .NET 程序集

c# - ASP.NET MVC。 Ajax.BeginForm OnSuccsess 找不到 javascript 函数

选择名为 YEAR 的列时出现 SQL 错误

sql-server - 企业项目的 Access+SQL Server 替代方案

SQL 按列名的第二个字母排序

c# - 如何正确取消 Parallel.Foreach?

c# - 使用转换器的数据触发器不起作用

sql - 转换为英式日期格式问题

sql - SQL事务复制对事务日志文件有什么影响

mysql - 我可以查询按两列排序吗?