我有两个表[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,但它并没有用于我的查询。 沉重的"排序"运营商正在执行计划。
以下是该计划的屏幕截图:
你能帮我解决问题或添加一些新的索引,以便我可以避免这样做吗?#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
答案 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)
执行计划:
糟糕的主意:
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
执行计划:
答案 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
我们没有直接控制执行计划(有充分理由为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)
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
现在查询计划在这里看起来很完美,没有在计划中使用段和序列项目迭代器对迭代器进行排序 被推回到查询计划中,似乎完美甚至是子树花费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
您可以在下面使用或不使用热缓存脚本来测试计算机的性能:
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*****************************************************'