Pertama, sebelum saya masuk ke cara mendapatkan Rencana Eksekusi Aktual dalam kode dan menemukan yang melaporkan membutuhkan indeks, saya sarankan Anda melihat menggunakan Penasihat Penyetelan Mesin Basis Data (DTA) , Anda dapat memberinya daftar semua kueri dan akan memprosesnya untuk memberi tahu Anda kemungkinan indeks, statistik, dan banyak hal lain yang dapat membantu merencanakan kueri Anda.
Bahkan lebih baik daripada memberikan daftar 1m+ kueri adalah Anda bisa mendapatkan jejak dari server dengan kueri aktual yang sedang dijalankan dan itu akan fokus pada kueri yang paling banyak memakan waktu.
Untuk menjawab pertanyaan awal Anda, Anda perlu menambahkan SET STATISTICS XML ON
di awal koneksi, ini akan memberi Anda data XML yang menjadi dasar GUI yang Anda tunjukkan. (Lihat di sini untuk info lebih lanjut tentang mendapatkan rencana
). Setelah Anda melakukannya, kueri Anda akan kembali dengan kumpulan hasil tambahan yang berisi xml untuk paket di baris pertama kolom pertama.
Berikut adalah fungsi cepat dan kotor yang melakukan itu.
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;
}
Dan inilah XML yang dikembalikan untuk kueri select TOTAL_SALES from clients where ACTIVE = 0;
yang saya jalankan yang saya miliki di salah satu database lokal saya.
<?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][email protected]" 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>
Sekarang, karena Microsoft cukup bagus, jika Anda menavigasi ke namespace yang tercantum dalam XML
Anda benar-benar bisa mendapatkan salinan .xsd
untuk formatnya. Anda kemudian dapat dari prompt perintah pengembang melakukan xsd showplanxml.xsd /classes
dan itu akan memberi Anda showplanxml.cs
yang dapat Anda gunakan dengan XmlSerializer
.
Berikut adalah contoh program kecil yang melakukan debugger break pada indeks yang hilang.
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();
}
Saya menggunakan XmlSerializer dan mendeseralisasinya ke kelas tetapi Anda dapat dengan mudah memuat ini ke XDocument kemudian menggunakan XPath
untuk menemukan semua node bernama MissingIndex
.