The percentages on each operator that are shown in an "actual" plan are still estimates. The additions to an "actual" plan over "estimated" are the actual row counts and operator executes.
In the example you've given the key lookup was estimated to be executed 3341 times, which is why it has a high estimated percentage cost.
One of the other operators is really consuming the time. Get a (free) copy of Plan Explorer from Sentry One, it'll make it easier to identify where.
If nothing stands out, start a new question and post the execution plan xml.
In general it is not possible to use an index seek on a condition x <> 1
and y <> 1
.
With an index on x,y
the best you can do is convert it into two range seeks (x < 1
and x > 1
) with a residual predicate on y <> 1
(and this wouldn't be able to use additional index key columns to avoid a sort)
For a bit
column as it can only have three values. 0
, 1
, NULL
logically WHERE bit_column <> 1
is equivalent to WHERE bit_column = 0
but seems SQL Server doesn't take advantage of that here and convert the <>
to =
conditions for you.
Adding a couple of check constraints does the job though even though these are apparently redundant in that they don't actually restrict the allowable values for the datatype in any way (for NULL
if a check constraint evaluates to UNKNOWN
it counts as passing)
CREATE TABLE MyTable
(
Foo INT,
IsFlag1 BIT NULL CHECK (IsFlag1 IN (0, 1)),
IsFlag2 BIT NULL CHECK (IsFlag2 IN (0, 1)),
SomeId INT
);
CREATE NONCLUSTERED INDEX ix
ON MyTable(IsFlag1, IsFlag2, SomeId)
INCLUDE (Foo);
The plan now does show a seek on IsFlag1 = 0 AND IsFlag2 = 0
Or alternatively this filtered index also avoids the need for a SORT
CREATE NONCLUSTERED INDEX ix
ON MyTable(SomeId)
INCLUDE (Foo,IsFlag1, IsFlag2)
WHERE IsFlag1 != 1 and IsFlag2 != 1
It does a scan of the filtered index (the qualifying rows ordered by SomeId
) with a TOP
to stop scanning after the 1,000 rows are retrieved. IsFlag1, IsFlag2
are INCLUDE
-d in the index to avoid an unnecessary look up that occurs without this.
Best Answer
The general execution model is a pipeline, where each iterator returns a row at a time. Execution starts at the root iterator (on the far left, labelled
SELECT
in your example).After initialization, the root iterator requests a row from its immediate child, and so on down the chain until an iterator that can return a row is found. This passes back up the chain to the root where it is queued for despatch to the client. That is a very simplified overview, for more details see:
The nonclustered index seek will locate a row that matches
col2=val2
. It will be able to return col2 and col1 (see its output list) because col1 is present in the index (since the primary key is clustered in this case).This row is passed up to the nested loops join, which then passes control to the key lookup. The lookup uses the col1 value to seek into the clustered index b-tree to find the value of col3 in this row. The value is tested against the predicate
col3=val3
and only returned if it matches.If there is a match, the row (c1, c2, c3) is passed up the chain and queued for transmission to the client. As control descends the tree again, any new match for col2 in the nonclustered index will result in a repeat of the nested loops join -> lookup -> return row cycle. As soon as the nonclustered index seek runs out of rows, the process completes when control next returns to the root iterator.
That's just the way the graphical plan is laid out. See the links and discussion above for the correct way to understand the execution process in detail.
Most likely yes. The optimizer makes a cost-based choice between the available strategies. With very few matches expected, the optimizer will usually assess a nonclustered index seek with a lookup as being the cheapest option. An exception occurs when the table is very small. In that case, scanning the clustered index and applying both predicates to each row will likely look cheapest.
As a final note, a covering nonclustered index that would avoid the lookup is:
It should be specified
UNIQUE
if that is true for (col2, col3).