Back to the technical posts…
I’m going to take an example of a very simple execution plan that I used in a presentation a few weeks back and go over it in some detail, pointing out information that is available and what can be read from the plan.
The execution plan can be downloaded (in xml format) here – ExecPlanDem01.zip
The query that the plan came from is a very simple two table query. It’s not a very optimal plan, but that’s because I forced an index hint in order to generate a more interesting plan. Without the hint, it’s a simple exec plan with two index seeks and a nested loop join.
The first thing that can be seen from the execution plan is that most of the cost of the query is in two operations, a key lookup (formerly called a bookmark lookup) and a clustered index seek. The high cost of the key lookup is a good sign that the query is using an inappropriate index. (in a future post I’ll discuss using the exec plan to choose indexes)
The second thing that can be seen is that there is a filter operator. This again suggests that there is not an appropriate index, as this plan means that SQL will first select rows from the table and later filter out rows that aren’t needed.
The first thing to notice about the index seek is that there are two seek predicates, one an equality, one an inequality. Having them both as seek predicates indicated that the columns in this index are in the correct order to support the two predicates. (I’ll do an entire post sometime on index key column ordering)
The second thing to note is that the estimated and actual rows are identical. This is good, it indicates that the statistics on this index are up to date and that the optimiser managed to get an accurate estimate of the rows that would be returned.
The presence of a key lookup operator indicates that the nonclustered index that was used to locate the rows affected by the query did not have all the columns required by the query. The missing columns must be looked up from the clustered index. The output list shows what columns were fetched by the key lookup. This can help when altering indexes to get a covering index.
One important thing to note is that the estimated rows for this operator is 1. That’s not a cardinality problem, it’s because the estimated rows is per execution of the operator and the actual rows is total over all executions. The number of times an operator is executed is stored within the XML, however the SQL 2005 management studio doesn’t show it. Management studio in SQL 2008 does.
This shows one of the reasons why the key lookup can be a bottleneck. For each row returned by the index seek, the key lookup does a clustered index seek returning a single row. For a small number of rows, that’s not too much or a problem, for hundreds of rows it can be a major bottleneck in the query.
The last interesting part of the execution plan is the filter.
Having a filter in the exec plan indicates that there was some condition that could not be evaluated as part of the index seek/scan. This may be because in involves an aggregate, because it involves columns in multiple tables or, as in this case, because the column was not part of the index and had to be looked up separatly.
As with the index seek, the estimated and actual row counts are very similar, indicating that the cardinality estimate is accurate for this query.
So, that’s most of the interesting things that can be gleaned from the exec plan. I have a couple more posts planned in the series on reading exec plans, one showing a more complex plan and the other going into more detail on the % costs that the plan shows.
That’s all for now. I’ll see if ‘i can dig up a more complex plan to work through.