4

我有一个查询优化问题。假设有一张包含所有发票的表格。使用 TVP(表值参数)我想通过提供 1..n 个 id 来选择少数记录,或者通过提供值为 -1 的单个 id 返回所有记录。

    DECLARE @InvoiceIdSet AS dbo.TBIGINT;
    INSERT INTO @InvoiceIdSet VALUES (1),(2),(3),(4)
    --INSERT INTO @InvoiceIdSet VALUES (-1)

    SELECT TOP 100
            I.Id ,
            Number ,
            DueDate ,
            IssuedDate ,
            Amount ,
            Test3
    FROM    dbo.Invoices I
    --WHERE   EXISTS ( SELECT NULL
    --                 FROM   @InvoiceIdSet
    --                 WHERE  I.Id = ID
    --                        OR ID = -1 )
    --CROSS APPLY @InvoiceIdSet s WHERE i.Id = s.ID OR s.ID = -1
    JOIN @InvoiceIdSet S ON S.ID = I.Id OR S.ID=-1

无论我使用哪种选择方法,查询的执行效率都很高,直到我开始使用 OR 运算符,此时它开始需要很长时间才能返回少量记录,但所有记录的返回速度都非常快。

任何指示和建议将不胜感激。

没有或

或

第一个计划没有 OR,第二个计划有 OR。

更新: 在摆弄了不同的选项之后,无论参数数量如何,我都认为这个解决方案是性能最快的。

首先更改 UserDefinedTableType 以包含主键索引:

CREATE TYPE [dbo].[TBIGINT] AS TABLE(
[ID] [bigint] NOT NULL PRIMARY KEY CLUSTERED
)

select 语句现在看起来像这样:

SELECT TOP 100
    I.Id ,
    Number ,
    DueDate ,
    IssuedDate ,
    Amount ,
    Test3
FROM    dbo.Invoices I
WHERE   I.ID IN ( SELECT    S.ID
              FROM      @InvoiceIdSet S
              WHERE     S.ID <> -1
              UNION ALL
              SELECT    S.ID
              FROM      dbo.Invoices S
              WHERE     EXISTS ( SELECT NULL
                                 FROM   @InvoiceIdSet
                                 WHERE  ID = -1 ) )

计划变得更大,但性能几乎保持不变,在少数(第一个计划)和所有(第二个计划)记录之间。

很少的记录

所有记录

正如您所看到的,这些计划现在是相同的,并且可以在不到一秒的时间内从 1M 行中返回所需的记录。

我很想听听社区对此解决方案的看法。

感谢大家的帮助。

4

3 回答 3

1

如果or S.ID=-1添加了,SQL Server 就知道每一行的条件都为真;因此,查询计划将使用 Scan 作为您的第二个计划。正如 Martin Smith 在评论中所说,SQL Server 在这种情况下不够聪明。您需要有 2 个查询(如果有 -1 则有一个,如果只选择了一些行,则有另一个)。这样,SQL Server 可以生成 2 个计划,并且对于它们所涵盖的场景来说,这两个计划都是最佳的。您也可以进行重新编译(但总是会重新编译,这通常会浪费资源)。或者您可以动态构建查询。动态意味着您将只生成 2 个查询,并且它们都将被缓存,因此无需重新编译,但要注意它是如何编写的,因此它不容易受到 SQL 注入的影响。

谢谢

于 2013-10-28T17:28:42.010 回答
1

我将在这里接受我自己的答案:

DECLARE @InvoiceIdSet AS TBIGINT
--INSERT  INTO @InvoiceIdSet
--VALUES  ( 1 ),
--        ( 2 ),
--        ( 3 ),
--        ( 4 )
INSERT  INTO @InvoiceIdSet VALUES  ( -1 )

SELECT TOP 100
        I.Id ,
        Number ,
        DueDate ,
        IssuedDate ,
        Amount ,
        Test3
FROM    dbo.Invoices I
WHERE   I.ID IN ( SELECT    S.ID
                  FROM      @InvoiceIdSet S
                  WHERE     NOT EXISTS ( SELECT NULL
                                         FROM   @InvoiceIdSet
                                         WHERE  ID = -1 )
                  UNION ALL
                  SELECT    S.ID
                  FROM      dbo.Invoices S
                  WHERE     EXISTS ( SELECT NULL
                                     FROM   @InvoiceIdSet
                                     WHERE  ID = -1 ) )

它适用于所有和某些场景。

于 2013-10-29T15:50:14.003 回答
0

看起来您在这里通过提供负 1 作为参数作为获取所有内容的方式来做一个技巧。

我假设这是在存储过程或其他东西中,所以可能在这种情况下提供一个 null 作为参数并尝试以下操作;

DECLARE @IDparam int

SELECT TOP 100
            I.Id ,
            Number ,
            DueDate ,
            IssuedDate ,
            Amount ,
            Test3
    FROM    dbo.Invoices I
    JOIN @InvoiceIdSet S ON S.ID = I.Id AND COALESCE(@IDparam, I.Id) = I.Id

如果@IDParam为 null,它将I.Id在 where 子句中使用。可能会加快速度。

于 2013-10-28T17:01:51.213 回答