Sql server OVER(按列排序)生成“排序”操作

Sql server OVER(按列排序)生成“排序”操作,sql-server,sql-server-2008,indexing,query-optimization,Sql Server,Sql Server 2008,Indexing,Query Optimization,我正在处理一个需要根据用户输入进行过滤、排序和分页的查询。现在我正在测试一个非常慢的案例,在检查查询计划时,“排序”占用了96%的时间 数据模型实际上并没有那么复杂,下面的查询应该足够清楚,以了解发生了什么: WITH OrderedRecords AS ( SELECT A.Id , A.col2 , ... , B.Id , B.col1 , ROW_NUMBER() OVER (OR

我正在处理一个需要根据用户输入进行过滤、排序和分页的查询。现在我正在测试一个非常慢的案例,在检查查询计划时,“排序”占用了96%的时间

数据模型实际上并没有那么复杂,下面的查询应该足够清楚,以了解发生了什么:

WITH OrderedRecords AS (
    SELECT 
        A.Id
        , A.col2
        , ...
        , B.Id
        , B.col1 
        , ROW_NUMBER() OVER (ORDER BY B.col1 ASC) AS RowNumber
    FROM A
    LEFT JOIN B ON (B.SomeThing IS NULL) AND (A.BId = B.Id)
    WHERE (A.col2 IN (...)) AND (B.Id IN (...))
)
SELECT
    *
FROM OrderedRecords WHERE RowNumber Between x AND y
A是一个包含约100k条记录的表,但在该字段中会增长到数千万条记录,而B是一个包含5条记录的类别类型表,它的增长量永远不会超过几条记录。A.Id和B.Id上有聚集索引

性能真的很糟糕,我想知道是否有可能以某种方式弥补这一点。例如,如果排序是在A.Id而不是B.col1上,那么一切都非常快。也许我可以优化B.col1是某种索引

我已经尝试在字段本身上添加索引,但这没有帮助。可能是因为表B中不同项目的数量本身非常少&与A相比


有什么想法吗?

我认为这可能是问题的一部分:

   LEFT JOIN B ON (B.SomeThing IS NULL) AND (A.Id = B.Id)
    WHERE (A.col2 IN (...)) AND (B.Id IN (...)

由于WHERE子句的存在,您的左连接在逻辑上就像一个内部连接,因为只返回特定的B.ID行。如果这是您的意图,那么继续使用内部联接,这可能会帮助优化器意识到您正在查找数量有限的行。

我认为这可能是问题的一部分:

   LEFT JOIN B ON (B.SomeThing IS NULL) AND (A.Id = B.Id)
    WHERE (A.col2 IN (...)) AND (B.Id IN (...)

由于WHERE子句的存在,您的左连接在逻辑上就像一个内部连接,因为只返回特定的B.ID行。如果这是您的意图,那么继续使用内部联接,这可能会帮助优化器意识到您正在寻找数量有限的行。

我建议您尝试以下操作

对于B表创建索引:

create index IX_B_1 on B (col1, Id, SomeThing)
create index IX_A_1 on A (col2, BId) include (Id, ...)
对于表的创建索引:

create index IX_B_1 on B (col1, Id, SomeThing)
create index IX_A_1 on A (col2, BId) include (Id, ...)
在include中,放入表A的所有其他列,这些列在SELECT of orderederRecords CTE中列出

然而,正如您所看到的,索引IX_A_1占用空间,并且可以占用大约表数据本身的大小

因此,您也可以尝试从索引的include部分中省略额外的列:

create index IX_A_2 on A (col2, BId) include (Id)
但在这种情况下,您必须稍微修改您的查询:

;WITH OrderedRecords AS (
    SELECT 
        AId = A.Id
        , A.col2
        -- remove other A columns from here
        , bid = B.Id
        , B.col1 
        , ROW_NUMBER() OVER (ORDER BY B.col1 ASC) AS RowNumber
    FROM A
    LEFT JOIN B ON (B.SomeThing IS NULL) AND (A.BId = B.Id)
    WHERE (A.col2 IN (...)) AND (B.Id IN (...))
)
SELECT
    R.*, A.OtherColumns
FROM OrderedRecords R
    join A on A.Id = R.AId
WHERE R.RowNumber Between x AND y

我建议你试试下面的方法

对于B表创建索引:

create index IX_B_1 on B (col1, Id, SomeThing)
create index IX_A_1 on A (col2, BId) include (Id, ...)
对于表的创建索引:

create index IX_B_1 on B (col1, Id, SomeThing)
create index IX_A_1 on A (col2, BId) include (Id, ...)
在include中,放入表A的所有其他列,这些列在SELECT of orderederRecords CTE中列出

然而,正如您所看到的,索引IX_A_1占用空间,并且可以占用大约表数据本身的大小

因此,您也可以尝试从索引的include部分中省略额外的列:

create index IX_A_2 on A (col2, BId) include (Id)
但在这种情况下,您必须稍微修改您的查询:

;WITH OrderedRecords AS (
    SELECT 
        AId = A.Id
        , A.col2
        -- remove other A columns from here
        , bid = B.Id
        , B.col1 
        , ROW_NUMBER() OVER (ORDER BY B.col1 ASC) AS RowNumber
    FROM A
    LEFT JOIN B ON (B.SomeThing IS NULL) AND (A.BId = B.Id)
    WHERE (A.col2 IN (...)) AND (B.Id IN (...))
)
SELECT
    R.*, A.OtherColumns
FROM OrderedRecords R
    join A on A.Id = R.AId
WHERE R.RowNumber Between x AND y

带有/的临时表比CTE的临时表更快,它们也允许索引,所以,您可以将一个临时表放在RowNumber列上。它是否改变了按B.col1,A.Id排序而不是按B.col1 ASC排序的任何内容?@SebStuij,您确定您在A.Id=B.Id上加入了A和B,而不是像A.CategoryId=B.Id这样的东西吗?该死的输入错误。。。你说得对,它应该是:和A.BId=B.Id临时表那些带有/可以比CTE更快的表,它们也允许索引,所以,你可以在RowNumber列上放一个。它是否会改变任何按B.col1,A.Id排序而不是按B.col1 ASC排序的表?@SebStuij,你确定你在A.Id=B.Id上加入了A和B,而不是像A.CategoryId=B.Id这样的东西吗?该死的,他们打字错误。。。你说得对,应该是这样的:A.BId=B.I我不确定我是否完全理解;我使用了左连接,因为B.SomeThing可能为NULL,在这种情况下,我确实希望返回a的记录。您指定的WHERE子句表示,您只希望a.ID在某个列表中,B.ID在某个列表中的行。这样想吧;您说过在JOIN子句中获取A中的所有行和B中的匹配行,然后您告诉优化器只返回B中与某个列表匹配的行。B是列吗?是的,in子句不在A.Id之上,而是在其他列之上column@SebStuij,的确,在WHERE子句中使用B.Id时,左连接是无用的。让它成为一个内部连接,或者把B.Id移到ON子句中,我不确定我是否完全理解;我使用了左连接,因为B.SomeThing可能为NULL,在这种情况下,我确实希望返回a的记录。您指定的WHERE子句表示,您只希望a.ID在某个列表中,B.ID在某个列表中的行。这样想吧;您说过在JOIN子句中获取A中的所有行和B中的匹配行,然后您告诉优化器只返回B中与某个列表匹配的行。B是列吗?是的,in子句不在A.Id之上,而是在其他列之上column@SebStuij,的确,在WHERE子句中使用B.Id时,左连接是无用的。使其成为内部联接,或者将B.Id向上移动到ON子句中