有没有办法以编程方式执行包含实际执行计划的查询,并查看是否有任何索引建议

时间:2014-09-16 22:41:54

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

我有很多查询,我想在sql server management studio上使用Include Actual Execution Plan功能测试每个查询

但是我不能手动执行1m +查询

所以我想我可以通过编程方式(来自c#)执行包含实际执行计划功能,并查看SQL服务器是否建议任何索引

enter image description here

1 个答案:

答案 0 :(得分:14)

首先,在我进入如何获取代码中的实际执行计划并找到报告需要索引的那些之前,我建议您查看使用Database Engine Tuning Adviser (DTA),您可以为其提供所有列表查询,它将处理它们,告诉您可能的索引,统计信息和许多其他有助于规划查询的事情。

比给它一个1m +查询列表更好的是,您可以从服务器获取正在运行的实际查询的跟踪,它将关注占用时间最多的查询。


要回答原始问题,您需要在连接开始时添加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>

现在,因为Microsoft非常好,如果你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的所有节点。