2

See the query below. The object and property names have been obfuscated somewhat to not leak confidential/sensitive information, but the query structure is the same.

When the .OrderBy(p => "") is added, which is complete non-sense to me, the query runs much faster. The time it takes to execute the query goes from approx. 2000ms down to approx. 400ms. I have tested it a couple of times, adding and removing only the OrderBy statement.

I am completely puzzled, how can this be? The query is executed on a SQL database in an Azure environment.

I can understand that ordering data on property A, and then selecting records where property A equals some value could potentialy speed up the query. But ordering on an empty string!? What is going on here?

Also I want to note, that the query, without the OrderBy, using Expressions ( as suggested in this post to circumvent SQL parameter sniffing) lowers the execution time also to approx. 400ms. Adding the .OrderBy(p => "") then doesn't make any noticeable difference.

        var query = (from p in Context.Punders.Where(p => p.A == A)
                    .Where(p => null != p.SomeNumber)
                    .Where(p => p.StatusCode == Default ||
                                   p.StatusCode == Cancelled)
                    .Where(p => p.DatePosted >= startDate && p.DatePosted <= endDate)
                join f in Context.Founders.Where(f => f.A == A) on p.Code equals f.Code
                join r in Context.Rounders.Where(r => r.A == A) on p.Code equals r.Code
                    into rg
                from r in rg.DefaultIfEmpty()
                join pt in Context.FishTypes.Where(ft => ft.A ==A) on p.Code equals pt.Code
                where r == null
                select new
                {
                    p.Code,
                    f.B,
                    f.C,
                    p.D,
                    p.E,
                    pt.F,
                    pt.G,
                    p.H
                })
            .OrderBy(p => "");

Query without the .OrderBy(...

SELECT [Filter1].[q]              AS [q], 
       [Filter1].[c1]                 AS [edoc], 
       [Filter1].[oc1]            AS [wnrdc], 
       [Filter1].[otc1]        AS [weener], 
       [Filter1].[ptc1]      AS [pmtpdc], 
       [Extent4].[isr]          AS [isr], 
       [Extent4].[rac] AS [rac], 
       [Filter1].[arn]              AS [arn] 
FROM   (SELECT [Extent1].[pcid]  AS [pcid1], 
               [Extent1].[edoc]            AS [c1], 
               [Extent1].[pmtpdc] AS [ptc1], 
               [Extent1].[q]        AS [q], 
               [Extent1].[arn]        AS [arn], 
               [Extent1].[dateposted]      AS [DatePosted], 
               [Extent2].[pcid]  AS [pcid2], 
               [Extent2].[wnrdc]       AS [oc1], 
               [Extent2].[weener]   AS [otc1] 
        FROM   [fnish].[post] AS [Extent1] 
               INNER JOIN [fnish].[olik] AS [Extent2] 
                       ON [Extent1].[olikedoc] = [Extent2].[edoc] 
               LEFT OUTER JOIN [fnish].[receivable] AS [Extent3] 
                            ON ( [Extent3].[pcid] = @p__linq__4 ) 
                               AND ( [Extent1].[edoc] = 
                                     [Extent3].[pepstedoc] ) 
        WHERE  ( [Extent1].[arn] IS NOT NULL ) 
               AND ( [Extent1].[posttedoc] IN ( N'D', N'X' ) ) 
               AND ( [Extent3].[id] IS NULL )) AS [Filter1] 
       INNER JOIN [fnish].[paymenttype] AS [Extent4] 
               ON [Filter1].[ptc1] = [Extent4].[edoc] 
WHERE  ( [Filter1].[pcid1] = @p__linq__0 ) 
       AND ( [Filter1].[dateposted] >= @p__linq__1 ) 
       AND ( [Filter1].[dateposted] <= @p__linq__2 ) 
       AND ( [Filter1].[pcid2] = @p__linq__3 ) 
       AND ( [Extent4].[pcid] = @p__linq__5 ) 

Query with the .OrderBy(...

SELECT [Project1].[q]              AS [q], 
       [Project1].[edoc]                  AS [edoc], 
       [Project1].[wnrdc]             AS [wnrdc], 
       [Project1].[weener]         AS [weener], 
       [Project1].[pmtpdc]       AS [pmtpdc], 
       [Project1].[isr]          AS [isr], 
       [Project1].[rac] AS [rac], 
       [Project1].[arn]              AS [arn] 
FROM   (SELECT N''                               AS [C1], 
               [Filter1].[c1]                 AS [edoc], 
               [Filter1].[ptc1]      AS [pmtpdc], 
               [Filter1].[q]              AS [q], 
               [Filter1].[arn]              AS [arn], 
               [Filter1].[oc1]            AS [wnrdc], 
               [Filter1].[otc1]        AS [weener], 
               [Extent4].[isr]          AS [isr], 
               [Extent4].[rac] AS [rac] 
        FROM   (SELECT [Extent1].[pcid]  AS [pcid1], 
                       [Extent1].[edoc]            AS [c1], 
                       [Extent1].[pmtpdc] AS [ptc1], 
                       [Extent1].[q]        AS [q], 
                       [Extent1].[arn]        AS [arn], 
                       [Extent1].[dateposted]      AS [DatePosted], 
                       [Extent2].[pcid]  AS [pcid2], 
                       [Extent2].[wnrdc]       AS [oc1], 
                       [Extent2].[weener]   AS [otc1] 
                FROM   [fnish].[post] AS [Extent1] 
                       INNER JOIN [fnish].[olik] AS [Extent2] 
                               ON [Extent1].[olikedoc] = [Extent2].[edoc] 
                       LEFT OUTER JOIN [fnish].[receivable] AS [Extent3] 
                                    ON ( [Extent3].[pcid] = 
                                         @p__linq__4 ) 
                                       AND ( [Extent1].[edoc] = 
                                             [Extent3].[pepstedoc] ) 
                WHERE  ( [Extent1].[arn] IS NOT NULL ) 
                       AND ( [Extent1].[posttedoc] IN ( N'D', N'X' ) ) 
                       AND ( [Extent3].[id] IS NULL )) AS [Filter1] 
               INNER JOIN [fnish].[paymenttype] AS [Extent4] 
                       ON [Filter1].[ptc1] = [Extent4].[edoc] 
        WHERE  ( [Filter1].[pcid1] = @p__linq__0 ) 
               AND ( [Filter1].[dateposted] >= @p__linq__1 ) 
               AND ( [Filter1].[dateposted] <= @p__linq__2 ) 
               AND ( [Filter1].[pcid2] = @p__linq__3 ) 
               AND ( [Extent4].[pcid] = @p__linq__5 )) AS [Project1] 
ORDER  BY [Project1].[c1] ASC 

Conclusion

From what I have learned, with a bit of a guess: It is case specific behavior. In my case, the performance gain is likely due to a different execution plan being constructed by the SQL server that is yielding a better performing query. I've seen a different execution plan with the query without the OrderBy using the SQL statement OPTION(RECOMIPILE) that showed similar performance gain. So adding the OrderBy to the LINQ query is very likely (I think) producing a different execution plan that yields a better performing query.

Community
  • 1
  • 1
Mike de Klerk
  • 11,906
  • 8
  • 54
  • 76
  • 4
    Run SQL profiller and check sql queries generated by EF – BWA Mar 23 '18 at 09:16
  • 1
    You can try LINQPad. It's an awesome tool. You write linq to sql or ef and you can immediately see the sql generated. (I'm not affiliated, just a user). That way, you can see how the SQL changes when you add the order by clause. – Mihai Caracostea Mar 23 '18 at 09:21
  • 2
    Given your note I suppose sql server cached plan for your query, and with given values this plan is ineffective. By adding order by you force it to create new execution plan (becsuse it is different query now). – Evk Mar 23 '18 at 09:22
  • @Evk What you state is in line with using Expressions to prevent parameter sniffing and thus not reuse a cached execution plan. – Mike de Klerk Mar 23 '18 at 09:25
  • 1
    Please update your question with the two SQL statements. Also, what happens if you clear your plan caches? https://stackoverflow.com/questions/8495210/resetting-execution-plans – mjwills Mar 23 '18 at 09:37
  • 1
    @mjwills Thanks for the reminder. I added them to the post. I see a 3rd subquery for the query produced with the `OrderBy`, So now it is clear to me that the performance gain is likely due to a different execution plan. As SQL `OPTION(RECOMPILE)` shows similar performance gain when running the 'original' SQL query via SQL Management Studio. – Mike de Klerk Mar 26 '18 at 06:42
  • 1
    Agreed, that seems the most likely explanation. – mjwills Mar 26 '18 at 06:44
  • @Evk Why don't your write a short answer to this question with respect to the execution plan (as you suggested)? I think this is the case indeed. So this post has an answer, I can accept it, and you get some credits. – Mike de Klerk Mar 26 '18 at 06:47

1 Answers1

2

Given your note

Also I want to note, that the query, without the OrderBy, using Expressions ( as suggested in this post to circumvent SQL parameter sniffing) lowers the execution time also to approx. 400ms. Adding the .OrderBy(p => "") then doesn't make any noticeable difference.

The most reasonable explanation is: OrderBy has the same effect as using explicit values instead of parameters. So if you had pre-cached plan for given query, and with particular parameter values this plan is not optimal (2 seconds) - changing this query by adding useless OrderBy to it will force SQL Server to create new execution plan for this query, and so will negate effect of old non-optimal execution plan. Of course, it should be clear that this is not a good way to negate plan caching.

Evk
  • 98,527
  • 8
  • 141
  • 191