【问题标题】:SQL Indexes to be used in WHERE statement在 WHERE 语句中使用的 SQL 索引
【发布时间】:2020-12-10 10:42:03
【问题描述】:

我有一个包含 3 个字段的表,我创建了一个复合索引。我需要它来检查记录是否存在。

这是我的桌子

tblGamePlayLog
UserId (PK)
LogId (PK)
...
...
ProviderId
ResellerId
GameId
...
...

-- ProviderId, ResellerId and GameId is indexed (composite index)

我有这样的存储过程

CREATE PROCEDURE [AS.uspProviderResellerGame_IsDeletable]
(
    @ProviderId INT = -1,    --Use -1 to ignore this field
    @ResellerId INT = -1,    --Use -1 to ignore this field
    @GameId INT = 1,         --Use -1 to ignore this field
    @IsDeletable BIT = 0 OUT
)
AS
BEGIN
    SET @IsDeletable = 1;
    
    ...
    ...
    ...
    ELSE IF (EXISTS(SELECT TOP 1 1 FROM [tblGamePlayLog] WHERE ((@ProviderId = -1) OR ([ProviderId] = @ProviderId)) AND ((@ResellerId = -1) OR ([ResellerId] = @ResellerId)) AND ((@GameId = -1) OR ([GameId] = @GameId)))) SET @IsDeletable = 0;
END;

此存储过程允许调用函数传递 -1 以忽略对特定字段的检查。但是,它会导致查询显着变慢(由于日志包含 100 万条记录,因此速度会慢 1000 倍)。

如果我去掉 -1 检查,速度会显着提高。

...
...
...
ELSE IF (EXISTS(SELECT TOP 1 1 FROM [tblGamePlayLog] WHERE ([ProviderId] = @ProviderId) AND ([ResellerId] = @ResellerId) AND ([GameId] = @GameId))) SET @IsDeletable = 0;

我怀疑,但添加 -1 检查,SQL 不使用索引检查。我的问题是,如何在 WHERE 子句中允许 -1 检查但保留索引检查。

【问题讨论】:

  • option(recompile) 可能会有所帮助。
  • 我同意@GMB 的观点,即OPTION (RECOMPILE) 查询提示将成为解决方案的一部分。但是您仍然需要考虑索引需要有谓词的最左边的列才能有效使用。您需要多个索引才能对您的用例进行高效查询。
  • 我尝试在存储过程中使用WITH RECOMPILE。还是很慢……
  • 我也使用了GMB推荐的OPTION (RECOMPILE),它提高了速度,但仍然很慢... :(

标签: sql sql-server stored-procedures query-optimization where-clause


【解决方案1】:

我会推荐option(recompile),因此数据库会评估每次运行的文字参数并相应地计算出适当的执行计划。重新编译的开销应该小于您现在为次优计划支付的价格。

IF(
    EXISTS(
        SELECT 1
        FROM [tblGamePlayLog] 
        WHERE 
                (@ProviderId = -1 OR [ProviderId] = @ProviderId) 
            AND (@ResellerId = -1 OR [ResellerId] = @ResellerId) 
            AND (@GameId     = -1 OR [GameId]     = @GameId)
    )
    OPTION (RECOMPILE)
)

然后,我们需要查看问题的索引部分。如果您希望查询在所有情况下都高效运行,则需要多个索引,基本上是所有可能的搜索参数组合:

(providerid)
(resellerid)
(gameid)
(providerid, resellerid)
(providerid, gameid)
(gameid, resellerid)
(providerid, resellerid, gameid)

我们可以分解一下:

(providerid, resellerid, gameid)
(providerid, gameid)
(gameid, resellerid)
(resellerid)

【讨论】:

  • 我收到一个错误incorrect syntax near 'OPTION'。它在EXIST() 语句之外工作。所以,我把它放到临时变量中,这样我就可以使用OPTION(RECOMPILE),但它仍然很慢......
  • @Sam 。 . .将OPTION 放在) 之前。
【解决方案2】:

您可能会发现具有正确索引的动态 SQL 更加一致:

DECLARE @sql NVARCHAR(max) = '
SELECT @exists = MAX(flag)
FROM (SELECT TOP (1) 1 as flag
      FROM [tblGamePlayLog] 
      WHERE 1=1 @WHERE
     ) gpl
';

DECLARE @WHERE NVARCHAR(MAX) = '';
SET @WHERE = @WHERE + 
           (CASE WHEN @ProviderId <> -1 THEN ' AND [ProviderId] = @ProviderId' ELSE '' END) +
           (CASE WHEN @ResellerId <> -1 THEN ' AND [ResellerId] = @ResellerId' ELSE '' END) +
           (CASE WHEN @ProviderId <> -1 THEN ' AND [GameId] = @GameId' ELSE '' END);

SET @sql = REPLACE(@sql, '@WHERE', @WHERE);

DECLARE @exists INT;

exec sp_executesql @sql,
                   N'@ProviderId int, @ResellerId int, @ProviderId int, @exists INT OUTPUT',
                   @exists=@exists OUTPUT;

IF (@exists = 1) BEGIN
     . . . 
END;

SQL 的构造和运行它的开销应该相对较小——无论如何您都需要运行查询。这将保证重新编译。您还需要确保表上有适当的索引,这需要很多组合(GMB 在那个答案中指出了这一点)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-12
    • 2013-10-16
    • 2017-01-27
    • 1970-01-01
    • 2018-07-23
    • 1970-01-01
    相关资源
    最近更新 更多