如何避免索引计划中的排序运算符

时间:2016-02-03 07:51:04

标签: sql sql-server performance indexing sql-server-2008-r2

我有两个表[LogTable]和[LogTable_Cross]。

下面是填充它们的架构和脚本:

 --Main Table

 CREATE TABLE [dbo].[LogTable]
    (
      [LogID] [int] NOT NULL
                    IDENTITY(1, 1) ,
      [DateSent] [datetime] NULL,
    )
 ON [PRIMARY]
GO
 ALTER TABLE [dbo].[LogTable] ADD CONSTRAINT [PK_LogTable] PRIMARY KEY CLUSTERED  ([LogID]) ON [PRIMARY]
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_DateSent] ON [dbo].[LogTable] ([DateSent] DESC) ON [PRIMARY]
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_DateSent_LogID] ON [dbo].[LogTable] ([DateSent] DESC) INCLUDE ([LogID]) ON [PRIMARY]
GO


--Cross table

 CREATE TABLE [dbo].[LogTable_Cross]
    (
      [LogID] [int] NOT NULL ,
      [UserID] [int] NOT NULL
    )
 ON [PRIMARY]
GO
 ALTER TABLE [dbo].[LogTable_Cross] WITH NOCHECK ADD CONSTRAINT [FK_LogTable_Cross_LogTable] FOREIGN KEY ([LogID]) REFERENCES [dbo].[LogTable] ([LogID])
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_Cross_UserID_LogID]
 ON [dbo].[LogTable_Cross] ([UserID])
 INCLUDE ([LogID])
GO


-- Script to populate them
 INSERT INTO [LogTable]
        SELECT TOP 100000
                DATEADD(day, ( ABS(CHECKSUM(NEWID())) % 65530 ), 0)
        FROM    sys.sysobjects
                CROSS JOIN sys.all_columns


 INSERT INTO [LogTable_Cross]
        SELECT  [LogID] ,
                1
        FROM    [LogTable]
        ORDER BY NEWID()

 INSERT INTO [LogTable_Cross]
        SELECT  [LogID] ,
                2
        FROM    [LogTable]
        ORDER BY NEWID()

 INSERT INTO [LogTable_Cross]
        SELECT  [LogID] ,
                3
        FROM    [LogTable]
        ORDER BY NEWID()


GO

我想选择已经给出userid的所有日志(来自LogTable)(将从交叉表LogTable_Cross中检查用户ID)。

我尝试了以下4种不同的查询:

 SELECT DMT.LogID ,
        ROW_NUMBER() OVER ( ORDER BY DMT.DateSent DESC ) AS RowNumber
 FROM   LogTable DMT
 WHERE  LogID IN ( SELECT   LogID
                   FROM     LogTable_Cross DMTP
                   WHERE    DMTP.UserID = 1 )

 SELECT DMT.LogID ,
        ROW_NUMBER() OVER ( ORDER BY DMT.DateSent DESC ) AS RowNumber
 FROM   LogTable DMT
 WHERE  EXISTS ( SELECT LogID
                 FROM   LogTable_Cross DMTP
                 WHERE  DMTP.LogID = DMT.LogID
                        AND DMTP.UserID = 1 )


 SELECT DMT.LogID ,
        ROW_NUMBER() OVER ( ORDER BY DMT.DateSent DESC ) AS RowNumber
 FROM   LogTable DMT
        JOIN ( SELECT   LogID
               FROM     LogTable_Cross DMTP
               WHERE    DMTP.UserID = 1
             ) T ON DMT.LogID = T.LogID                            


 SELECT DMT.LogID ,
        ROW_NUMBER() OVER ( ORDER BY DMT.DateSent DESC ) AS RowNumber
 FROM   LogTable DMT
        INNER JOIN LogTable_Cross DMTP ON DMTP.LogID = DMT.LogID
 WHERE  DMTP.UserID = 1 

我面临的问题是,尽管我已经将非聚集索引应用于DateSent desc的LogTable,但它并没有用于我的查询。 沉重的"排序"运营商正在执行计划。

以下是该计划的屏幕截图:

enter image description here

你能帮我解决问题或添加一些新的索引,以便我可以避免这样做吗?#34;排序"运营商进入计划?

编辑:

即使我删除了Row_Number(),排序运算符仍然会出现以下查询:

 SELECT DMT.LogID  
 FROM   LogTable DMT
 WHERE  LogID IN ( SELECT   LogID
                   FROM     LogTable_Cross DMTP
                   WHERE    DMTP.UserID = 1 )
ORDER BY DMT.DateSent DESC 

以下是此查询的执行计划: enter image description here

3 个答案:

答案 0 :(得分:0)

在删除row_number()(窗口函数)之前,您无法避免执行计划中的排序操作,因为它需要asc或desc命令。

在您的情况下,您将根据desc的Datesent列顺序生成行号,因此需要排序。

ROW_NUMBER() OVER ( ORDER BY DMT.DateSent DESC ) AS RowNumber

如果要从执行计划中删除排序操作,请从SQL中删除rownumber列。

或者你可以尝试这样

SELECT DMT.LogID ,
        ROW_NUMBER() OVER (ORDER BY (SELECT null)) AS RowNumber
 FROM   LogTable DMT
 WHERE  LogID IN ( SELECT   LogID
                   FROM     LogTable_Cross DMTP
                   WHERE    DMTP.UserID = 1 )

在这种情况下,将删除排序操作,但不会根据任何特定顺序生成行号。但是我们从计划中删除了排序运算符,这正是您所期望的。

答案 1 :(得分:0)

我的选择:

CREATE UNIQUE /* !!! */ NONCLUSTERED INDEX IX_LogTable_DateSent_LogID
    ON dbo.LogTable (LogID)
    INCLUDE (DateSent) --WITH(DROP_EXISTING=ON)
GO
CREATE UNIQUE /* !!! */ NONCLUSTERED INDEX IX_LogTable_Cross_LogID
    ON dbo.LogTable_Cross (LogID)
    WHERE UserID = 1 --WITH(DROP_EXISTING=ON)
GO

SELECT t.LogID, ROW_NUMBER() OVER (ORDER BY t.DateSent DESC) AS RowNumber
FROM dbo.LogTable t
WHERE EXISTS(
        SELECT 1
        FROM dbo.LogTable_Cross c
        WHERE c.UserID = 1
            AND c.LogID = t.LogID
    )
--OPTION (MAXDOP 1)

执行计划:

enter image description here

糟糕的主意:

ALTER TABLE dbo.LogTable_Cross
    ADD DateSent DATETIME
GO

UPDATE i
SET DateSent = t.DateSent
FROM dbo.LogTable_Cross i
JOIN dbo.LogTable t ON i.LogID = t.LogID
GO

CREATE TRIGGER dbo.trg_IU_DataSent
    ON dbo.LogTable_Cross
    INSTEAD OF INSERT
AS 
BEGIN

    SET NOCOUNT ON;

    INSERT INTO dbo.LogTable_Cross (LogID, UserID, DateSent) 
    SELECT i.LogID, i.UserID, t.DateSent
    FROM INSERTED i
    JOIN dbo.LogTable t ON i.LogID = t.LogID

END
GO

CREATE /*UNIQUE*/ NONCLUSTERED INDEX ix
    ON dbo.LogTable_Cross (DateSent, LogID) WHERE UserID = 1
    --WITH (DROP_EXISTING=ON)
GO

SELECT LogID, ROW_NUMBER() OVER (ORDER BY DateSent DESC) AS RowNumber
FROM dbo.LogTable_Cross
WHERE UserID = 1

执行计划:

enter image description here

答案 2 :(得分:0)

同意与@raj(OP)排序迭代器可以避免使用行号生成结果但是对于特定数据我希望下面有索引:

CREATE  UNIQUE NONCLUSTERED INDEX  IX_DateSent_LogID  on  LogTable (DateSent  ,LogID )
CREATE UNIQUE  NONCLUSTERED INDEX  LogTable_Cross ON LogTable_Cross (UserID , LogID) 

现在在这里(将所有数据知识传递给数据库引擎,以便QO可以获取该知识的优势并制定更好的计划)         index IX_DateSent_LogID是唯一(单例搜索)并在DateSent,LogID和         index unique和LogTable_Cross按UserID,LogID

排序
    Now one can think query optimizer will not use  SORT 
    But not the case below:



SELECT DateSent , t.LogID, ROW_NUMBER() OVER (ORDER BY t.DateSent DESC) AS RowNumber
FROM dbo.LogTable t
WHERE EXISTS(
        SELECT 1
        FROM dbo.LogTable_Cross c
        WHERE c.UserID in ( 1) 
            AND c.LogID = t.LogID
    )

EXECUTAION PLAN 1

enter image description here

我们没有直接控制执行计划(有充分理由为offcorse) 但是人们可以认为循环使用Logtable可能是一个避免SORT的好主意 所以我们可以使用循环连接提示来做到这一点

SELECT DateSent , t.LogID, ROW_NUMBER() OVER (ORDER BY t.DateSent DESC) AS RowNumber
FROM dbo.LogTable t
WHERE t.LogID in (
        SELECT LogID
        FROM dbo.LogTable_Cross c
        WHERE c.UserID =  1

    )
Option (loop join)

执行计划2 enter image description here

Now you can see sort iterator is gone reason is simple for every row of index *IX_datesend_logid* is looping through
in logtable_cross for every row and returning mathing key on the bases id "LOGID" 

但为什么QO没有选择这个计划?

Reason is "SUBTREE COST"

使用循环加入“17.362”与“5.69605”进行比较合并加入     但是在你的硬件循环连接(无阻塞迭代器)上可能胜过合并连接和温缓存上的排序迭代器 但我不是强迫计划的忠实粉丝,

作为,您可能知道以不同方式表达逻辑要求     sql server可能会通过不同的代码路径生成不同的查询计划以满足要求。

您可以尝试CTE版本来获取数据

 ;  WITH t as

        (
        SELECT    t.LogID, ROW_NUMBER() OVER (ORDER BY t.DateSent DESC) AS RowNumber
FROM dbo.LogTable t

        )


 Select    t.LogID , T.RowNumber from t
WHERE EXISTS(
        SELECT 1
        FROM dbo.LogTable_Cross c
        WHERE c.UserID = 1  
            AND c.LogID = t.LogID
    )

执行计划3

enter image description here

现在查询计划在这里看起来很完美,没有在计划中使用段和序列项目迭代器对迭代器进行排序 被推回到查询计划中,似乎完美甚至是子树花费2.10024。

如果仍然不满意您可以尝试以下版本,因为 Logid是Ever-Increase-Unique-Clustered-Index,因此我们也可以像下面那样制定查询

    Declare @minid int , @maxid int
     SELECT @minid  = min(LogID) , @maxid  = max (logid )
        FROM dbo.LogTable_Cross c
        WHERE c.UserID =1  

  SELECT    t.LogID, ROW_NUMBER() OVER (ORDER BY t.DateSent DESC) AS RowNumber
FROM dbo.LogTable t
where LogID > =@minid and   LogID < = @maxid

执行计划4

enter image description here

  

您可以在下面使用或不使用热缓存脚本来测试计算机的性能:

SET STATISTICS TIME ON



SELECT   t.LogID, ROW_NUMBER() OVER (ORDER BY t.DateSent DESC) AS RowNumber
FROM dbo.LogTable t
WHERE EXISTS(
        SELECT 1
        FROM dbo.LogTable_Cross c
        WHERE c.UserID in ( 1) 
            AND c.LogID = t.LogID
    )
    option (loop join)



    print 'loop join *****************************************************'



SELECT   t.LogID, ROW_NUMBER() OVER (ORDER BY t.DateSent DESC) AS RowNumber
FROM dbo.LogTable t
WHERE EXISTS(
        SELECT 1
        FROM dbo.LogTable_Cross c
        WHERE c.UserID in ( 1) 
            AND c.LogID = t.LogID
    )




    print ' Merge join*****************************************************'

    ;   WITH t as

        (
        SELECT    t.LogID, ROW_NUMBER() OVER (ORDER BY t.DateSent DESC) AS RowNumber
FROM dbo.LogTable t

        )


 Select    t.LogID , T.RowNumber from t
WHERE EXISTS(
        SELECT 1
        FROM dbo.LogTable_Cross c
        WHERE c.UserID  =  1 
            AND c.LogID = t.LogID
    )



    --Logid is everincring unique clustered index so we can also formulate the query like below



    print 'CTE VERSION*****************************************************'

    declare @minid int , @maxid int
     SELECT @minid  = min(LogID) , @maxid  = max (logid )
        FROM dbo.LogTable_Cross c
        WHERE c.UserID =1  

  SELECT    t.LogID, ROW_NUMBER() OVER (ORDER BY t.DateSent DESC) AS RowNumber
FROM dbo.LogTable t
where LogID > =@minid and   LogID < = @maxid

print 'Somthing diffrent*****************************************************'