3

I have a question about indexer and execution plans in T-SQL.

My database is SQL Server 2008.

I have a simple three-table db schema:

database schema

The InverterData table is very large and partitioned (28,880,436 rows).

The Date column is calculated like this:

[Date] AS (ISNULL(CONVERT([date], [TimeStamp]), CONVERT([DATE], '19000101', (112)))) PERSISTED NOT NULL,

There is also a index for this column:

CREATE NONCLUSTERED INDEX [NonClusteredIndex] 
ON [InverterData] ([Date] DESC, [InverterID] ASC)

Select query #1:

I now want to make a simple select that include all three tables and the 'Date' column in a where clause:

SELECT 
    [TimeStamp], [ACPower], [DCPower]
FROM 
    [InverterData]
JOIN 
    [Inverter] ON [InverterData].[InverterID] = [Inverter].[ID]
JOIN 
    [DataLogger] ON [Inverter].[DataLoggerID] = [DataLogger].[ID]
WHERE 
    [InverterData].[Date] = '05.01.2016'
    AND [DataLogger].[ProjectID] = 20686

It took round about 19 seconds on me current database (result ~80 rows).

This is the execution plan:

slow query by date

Select query #2:

In the first select I detected that there is a long duration index seek for the 'Date' column. So I run a second select that only include the primary key column 'TimeStamp'.

This is the second select:

SELECT 
    [TimeStamp], [ACPower], [DCPower]
FROM 
    [InverterData]
JOIN 
    [Inverter] ON [InverterData].[InverterID] = [Inverter].[ID]
JOIN 
    [DataLogger] ON [Inverter].[DataLoggerID] = [DataLogger].[ID]
WHERE 
    [TimeStamp] >= '05.01.2016' AND [TimeStamp] < '06.01.2016'
    AND [DataLogger].[ProjectID] = 20686

It took only about 2 seconds on me current database.

This is the execution plan:

fast query by timestamp

Question:

Why are there two index seeks? I included all used columns from select 1 in one index. Why did it took so much longer?

details

Update 1:

If someone needed the complete schema I will add it to a SQL fiddle and post it.

Update 2:

Tooltip of index seek

details2

1
  • 1
    @SteffenMangold looks more readable, true. But if you had used [TimeStamp] >= '20160105' AND [TimeStamp] < '20160106', you may not needed the extra computed column at all. Commented Jan 6, 2016 at 13:21

2 Answers 2

4

In your first query the index is used to find rows matching [InverterData].[Date] = '05.01.2016' then it needs to lookup the rest of the row data to satisfy being able to return ACPower and DCPower - if you remove these columns from the output you'll see the extra lookup go away.

You could include the extra columns in the index with:

CREATE NONCLUSTERED INDEX [NonClusteredIndex] 
ON [InverterData] ([Date] DESC, [InverterID] ASC) INCLUDE ([ACPower], [DCPower])

This removes the extra lookup via the clustered index at the expense of making the non-clustered index consume more space on disk (and in memory). This query speed and used space trade-off is something you'll have to decide upon by running benchmarks on the bits of the application that use that table.

Note that you could also do:

CREATE NONCLUSTERED INDEX [NonClusteredIndex] 
ON [InverterData] ([Date] DESC, [InverterID] ASC, [ACPower], [DCPower])

which uses the extra columns as part of the key rather than just INCLUDING them. The former example is likely to be more efficient as the values are unlikely to be filtered/sorted upon so it saves page splits caused by the engine trying to keep the (effectively random) values stored in order.

4
  • Thank you! Perfect answer, I don't know that the "bookmark lookup" from the index to the row data took so many time. Now my query time drop from 12sec to far under 1sec. As feedback: there is almost no difference in time or size between INCLUDE or not in my scenario. Commented Jan 6, 2016 at 20:27
  • 1
    The time difference for query of newly created indexes would be zero. But the overhead of data change may differ a bit. Commented Jan 7, 2016 at 1:14
  • 1
    @SteffenMangold: For queries immediately after creating the index there will be no difference between the two versions, the difference with INCLUDE is that it doesn't need to keep the index in the order of those (effectively random) columns which leads to far fewer page splits on UPDATE and INSERT operations. As well as slowing changes this will over time lead to greater fragmentation that will slow down reads too and make your data take more space. So if you are only adding columns to an index to reduce extra references to the clustered index or heap, use INCLUDE. Commented Jan 7, 2016 at 9:54
  • Also, the lookup of more data from the clustered index (or heap) is not always significant. If only a couple of rows are being inspected the difference is negligible. In some cases where it is significant rearrangements of the statement/procedures affected can be a better optimisation than taking extra space by enlarging the index. This is one of those places where you need to use your knowledge of the application(s) and/or specific benchmarks to decide the better way to go. Commented Jan 8, 2016 at 14:24
3

The execution plans seems fine to me and logical.

In your first query, you're doing an index seek on your nonclustered index on your Date column and then doing a clustered key lookup (I think, my german is rusty to say the least, but from context it maches).

Is not a 'two seeks' operation. It's a seek and get additional data.

In your second query you're not using your non-clustered index because you qualified your query with timestamp column and then it can use your clustered index (in your case also primary key), which have all the data and therefore does not need to look up additional.

It seems sensible enough.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.