4

In a SQL Server 2012 version 11.0.5058 I've a query like this

SELECT TOP 30 
    row_number() OVER (ORDER BY SequentialNumber ASC) AS [row_number], 
    o.Oid, StopAzioni 
FROM 
    tmpTestPerf O 
INNER JOIN
    Stati s on O.Stato = s.Oid
WHERE 
    StopAzioni = 0
  • When I use ORDER BY SequentialNumber ASC it takes 400 ms
  • When I use ORDER BY DESC in the row_number function it takes only 2 ms

(This is in a test environment, in production it is 7000, 7 seconds vs 15 ms!)

Analyzing the execution plan, I found that it's the same for both queries. The interesting difference is that in the slower it works with all the rows filtered by the stopazioni = 0 condition, 117k rows

In the faster it only uses 53 rows

There are a primary key on the tmpTestPerf query and an indexed ASC key on the sequential number column.

How it could be explained?

Regards. Daniele

This is the script of the tmpTestPerfQuery and Stati query with their indexes

CREATE TABLE [dbo].[tmpTestPerf]
(
    [Oid] [uniqueidentifier] NOT NULL,
    [SequentialNumber] [bigint] NOT NULL,
    [Anagrafica] [uniqueidentifier] NULL,
    [Stato] [uniqueidentifier] NULL,

    CONSTRAINT [PK_tmpTestPerf] 
      PRIMARY KEY CLUSTERED ([Oid] ASC)
)

CREATE NONCLUSTERED INDEX [IX_2] 
   ON [dbo].[tmpTestPerf]([SequentialNumber] ASC)

CREATE TABLE [dbo].[Stati]
(
    [Oid] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
    [Descrizione] [nvarchar](100) NULL,
    [StopAzioni] [bit] NOT NULL

    CONSTRAINT [PK_Stati] 
      PRIMARY KEY CLUSTERED ([Oid] ASC)
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [iStopAzioni_Stati] 
   ON [dbo].[Stati]([StopAzioni] ASC)
GO
Mikael Eriksson
  • 136,425
  • 22
  • 210
  • 281
DBO
  • 41
  • 1

1 Answers1

6

The query plans are not exactly the same.

Select the Index Scan operator.

enter image description here

Press F4 to view the properties and have a look at Scan Direction.

When you order ascending the Scan Direction is FORWARD and when you order descending it is BACKWARD.

The difference in number of rows is there because it takes only 53 rows to find 30 rows when scanning backwards and it takes 117k rows to find 30 matching rows scanning forwards in the index.

Note, without an order by clause on the main query there is no guarantee on what 30 rows you will get from your query. In this case it just happens to be the first thirty or the last thirty depending on the order by used in row_number().

Mikael Eriksson
  • 136,425
  • 22
  • 210
  • 281
  • thanks Mikael. obviously there are little differences and one is the scan direction of the index scan, but the rest of the macro operations made on the database are the same. There is no order by in this query because of an example taken from a more complex query and it don't influence the rest of the job – DBO Oct 17 '14 at 13:31
  • 1
    @DBO Yes. It is a matter of how many rows must be read before finding the 30 you have specified in the top clause. The scan operator stops scanning when the query has returned 30 rows and that happens faster when scanning forward then when scanning backwards. – Mikael Eriksson Oct 17 '14 at 13:40
  • the problem and the strange is that my query is faster in descending! and the indexed column is in ascending – DBO Oct 17 '14 at 13:47
  • @DBO I read that wrong. Does however not matter at all. I will update the answer. It is possible to do a backward scan using an ascending index. – Mikael Eriksson Oct 17 '14 at 13:48
  • @MikaelEriksson would it help to write the query with an `EXISTS` subquery instead of the join? – ypercubeᵀᴹ Oct 22 '14 at 10:00
  • @ypercube i think it would be optimized to the same plan but I have not tried it. – Mikael Eriksson Oct 22 '14 at 10:16
  • 2
    Oh, I read the question and the answer again. You are right, the distribution of rows with stop=0 is the issue. – ypercubeᵀᴹ Oct 22 '14 at 11:01
  • This rewrite *might* solve the issue (or make it worse). It would still not do an explicit order by to select the rows and the result would still be arbitrary: `WITH top30 AS (SELECT TOP 30 SequentialNumber, o.Oid FROM tmpTestPerf O INNER JOIN Stati s on O.Stato = s.Oid WHERE StopAzioni = 0) SELECT row_number() OVER (ORDER BY SequentialNumber ASC) AS [row_number], Oid, 0 AS StopAzioni FROM top30 ;` – ypercubeᵀᴹ Oct 22 '14 at 11:05