SQL Server不在存储过程中使用索引

Jan*_*oom 11 t-sql sql-server indexing

我没有通过使用存储过程解决这个问题,但我们已经决定超越SP并只执行普通的'SQL

请参阅下面的扩展表方案
编辑2:更新索引(不再使用actieGroep)
NB.SQL Server 2005 Enterprise 9.00.4035.00
NB2.似乎与http://www.sqlservercentral.com/Forums/Topic781451-338-1.aspx有关

我在桌子上有两个索引:

  • statistiekId上的聚簇PK索引
  • foreignId上的非聚集索引

我有以下代码:

DECLARE @fid BIGINT
SET @fid = 873926

SELECT foreignId
FROM STAT_Statistieken
WHERE foreignId = @fid
Run Code Online (Sandbox Code Playgroud)

这按照它应该的方式执行; 它指向正确的索引,它只是扫描索引.

现在我正在创建一个存储过程:

ALTER PROCEDURE MyProcedure (@fid BIGINT)
AS BEGIN
    SELECT foreignId
    FROM STAT_Statistieken
    WHERE foreignId = @fid
END
Run Code Online (Sandbox Code Playgroud)

运行的东西:

EXEC MyProcedure @fid = 873926
Run Code Online (Sandbox Code Playgroud)

现在它正在我的PK索引上运行聚集索引扫描!Wtf还在继续?

所以我把SP改成了

SELECT foreignId
FROM STAT_Statistieken
    WITH (INDEX(IX_STAT_Statistieken_2))
WHERE foreignId = @fid
Run Code Online (Sandbox Code Playgroud)

现在它给出了:由于此查询中定义的提示,查询处理器无法生成查询计划.重新提交查询而不指定任何提示,也不使用SET FORCEPLAN.虽然同样的功能正在运行,就像它直接执行时一样.


额外信息:可以重现此行为的完整方案(评论中的英文名称)

CREATE TABLE [dbo].[STAT_Statistieken](
    [statistiekId] [bigint] IDENTITY(1,1) NOT NULL,
    [foreignId] [bigint] NOT NULL,
    [datum] [datetime] NOT NULL, --date
    [websiteId] [int] NOT NULL,
    [actieId] [int] NOT NULL, --actionId
    [objectSoortId] [int] NOT NULL, --kindOfObjectId
    [aantal] [bigint] NOT NULL, --count
    [secondaryId] [int] NOT NULL DEFAULT ((0)),
    [dagnummer]  AS (datediff(day,CONVERT([datetime],'2009-01-01 00:00:00.000',(121)),[datum])) PERSISTED, --daynumber
    [actieGroep]  AS (substring(CONVERT([varchar](4),[actieId],0),(1),(1))) PERSISTED,
    CONSTRAINT [STAT_Statistieken_PK] PRIMARY KEY CLUSTERED --actionGroup
    (
        [statistiekId] ASC
    )WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
    ) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)

指数

CREATE NONCLUSTERED INDEX [IX_STAT_Statistieken_foreignId_dagnummer_actieId_secondaryId] ON [dbo].[STAT_Statistieken] 
(
    [foreignId] ASC,
    [dagnummer] ASC,
    [actieId] ASC,
    [secondaryId] ASC
)WITH (PAD_INDEX  = ON, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, FILLFACTOR = 80, ONLINE = OFF) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)

执行

SET NOCOUNT ON;

    DECLARE @maand INT, @jaar INT, @foreignId BIGINT
    SET @maand = 9
    SET @jaar = 2009
    SET @foreignId = 828319


DECLARE @startDate datetime, @endDate datetime
SET @startDate = DATEADD(month, -1, CONVERT(datetime,CAST(@maand AS varchar(3))+'-01-'+CAST(@jaar AS varchar(5))))
SET @endDate = DATEADD(month, 1, CONVERT(datetime,CAST(@maand AS varchar(3))+'-01-'+CAST(@jaar AS varchar(5))))

DECLARE @firstDayDezeMaand datetime
SET @firstDayDezeMaand = CONVERT(datetime, CAST(@jaar AS VARCHAR(4)) + '/' + CAST(@maand AS VARCHAR(2)) + '/1')

DECLARE @daynumberFirst int
set @daynumberFirst = DATEDIFF(day, '2009/01/01', @firstDayDezeMaand)

DECLARE @startDiff int
SET @startDiff = DATEDIFF(day, '2009/01/01', @startDate)

DECLARE @endDiff int
SET @endDiff = DATEDIFF(day, '2009/01/01', @endDate)

SELECT @foreignId AS foreignId,
    SUM(CASE WHEN dagnummer >= @daynumberFirst THEN (CASE WHEN actieId BETWEEN 100 AND 199 THEN aantal ELSE 0 END) ELSE 0 END) as aantalGevonden, 
    SUM(CASE WHEN dagnummer >= @daynumberFirst THEN (CASE WHEN actieId BETWEEN 200 AND 299 THEN aantal ELSE 0 END) ELSE 0 END) as aantalBekeken, 
    SUM(CASE WHEN dagnummer >= @daynumberFirst THEN (CASE WHEN actieId BETWEEN 300 AND 399 THEN aantal ELSE 0 END) ELSE 0 END) as aantalContact,
    SUM(CASE WHEN dagnummer < @daynumberFirst THEN (CASE WHEN actieId BETWEEN 100 AND 199 THEN aantal ELSE 0 END) ELSE 0 END) as aantalGevondenVorige, 
    SUM(CASE WHEN dagnummer < @daynumberFirst THEN (CASE WHEN actieId BETWEEN 200 AND 299 THEN aantal ELSE 0 END) ELSE 0 END) as aantalBekekenVorige, 
    SUM(CASE WHEN dagnummer < @daynumberFirst THEN (CASE WHEN actieId BETWEEN 300 AND 399 THEN aantal ELSE 0 END) ELSE 0 END) as aantalContactVorige
FROM STAT_Statistieken
WHERE
    dagnummer >= @startDiff
    AND dagnummer < @endDiff
    AND foreignId = @foreignId 
OPTION(OPTIMIZE FOR (@foreignId = 837334, @startDiff = 200, @endDiff = 300))
Run Code Online (Sandbox Code Playgroud)

DBCC统计

Name                                                          | Updated               | Rows      | Rows smpl | Steps | Density | Avg. key | String index
IX_STAT_Statistieken_foreignId_dagnummer_actieId_secondaryId    Oct  6 2009  3:46PM 1245058    1245058    92    0,2492834    28    NO

All Density  | Avg. Length | Columns
3,227035E-06    8    foreignId
2,905271E-06    12    foreignId, dagnummer
2,623274E-06    16    foreignId, dagnummer, actieId
2,623205E-06    20    foreignId, dagnummer, actieId, secondaryId
8,031755E-07    28    foreignId, dagnummer, actieId, secondaryId, statistiekId

RANGE HI | RANGE_ROWS | EQ_ROWS | DISTINCT_RANGE_ROWS | AVG_RANGE ROWS
-1         0            2         0                     1
1356       3563         38        1297                  2,747109
8455       14300        29        6761                  2,115072
Run Code Online (Sandbox Code Playgroud)

并且如执行计划中所示使用索引.当我用这个params的程序包装它时:

@foreignId bigint,
@maand int, --month
@jaar int --year
Run Code Online (Sandbox Code Playgroud)

并运行它 _SP_TEMP @foreignId = 873924, @maand = 9, @jaar = 2009

它进行聚簇索引扫描!

Jus*_*ant 7

[编辑]

下面的PERSISTED-not-used-used问题仅在我的系统上使用actieGroep/actieId发生(SQL 2008).但是,使用dagnummer/datum列也可能在SQL 2005系统上发生同样的问题.如果确实发生了这种情况,它将解释您所看到的行为,因为需要聚集索引扫描来过滤数据值.要诊断这是否确实发生,只需将基准列作为INCLUDE-d列添加到索引中,如下所示:

CREATE NONCLUSTERED INDEX [IX_STAT_Statistieken_1] ON [dbo].[STAT_Statistieken]  
(  
    [foreignId] DESC,  
    [dagnummer] DESC,  
    [actieId] ASC,   
    [aantal] ASC    
) INCLUDE (datum)  ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)

如果这个索引修订版本的问题消失了,那么你知道dagnummer就是问题 - 你甚至可以从索引中删除dagnummer,因为SQL还没有使用它.

此外,修改索引以添加actieId是一个好主意,因为它避开了下面提到的问题.但是在此过程中,您还需要将aantal列保留在索引中,以便您的索引将成为此查询的覆盖索引.否则,SQL必须读取您的聚簇索引以获取该列的值.这将减慢您的查询速度,因为查找到聚簇索引非常慢.

[结束编辑]

这里有一堆想法可以帮助你解决这个问题,最有可能/最简单的事情:

  • 当我尝试重新编写你的使用模式和查询(使用伪生成的数据)时,我看到你的PERSISTED计算列actieGroep在运行时重新加载,而不是使用的持久值.这看起来像是SQL Server优化器中的一个错误.由于您的覆盖索引IX_STAT_Statistieken_1索引中不存在基础列值actieGroep (只有计算列存在),如果SQL Server确定它需要获取该附加列,SQL可能会认为聚簇索引比使用非索引更便宜聚簇索引,然后查找集群索引中每个匹配行的actieId.这是因为与顺序I/O相比,聚簇索引查找非常昂贵,因此任何需要查找超过百分之几行的计划都可能比扫描更便宜.在任何情况下,如果这确实是您所看到的问题,那么添加actieGroep作为IX_STAT_Statistieken_1索引的INCLUDE-d列应解决此问题.像这样:

    CREATE NONCLUSTERED INDEX [IX_STAT_Statistieken_1] ON [dbo].[STAT_Statistieken]
    (
    [foreignId] DESC,
    [secondaryId] ASC,
    [actieGroep] ASC,
    [dagnummer] DESC,
    [aantal] ASC
    ) INCLUDE (actieId) ON [PRIMARY]

  • 计算列actieGroep的数据类型是一个字符串,但您要将它与WHERE子句和CASE语句中的整数(例如IN(1,2,3))进行比较.如果SQL决定转换列而不是常量,则会损害查询性能并且可能使计算列扩展问题(如上所述)更有可能.我强烈建议将计算列定义更改为整数类型,例如

    CASE WHEN actieId BETWEEN 0 AND 9 THEN actieId
    WHEN actieId BETWEEN 10 AND 99 THEN actieId/10
    WHEN actieId BETWEEN 100 AND 999 THEN actieId/100
    WHEN actieId BETWEEN 1000 AND 9999 THEN actieId/1000
    WHEN actieId BETWEEN 10000 AND 99999 THEN actieId/10000
    WHEN actieId BETWEEN 100000 AND 999999 THEN actieId/100000
    WHEN actieId BETWEEN 1000000 AND 9999999 THEN actieId/1000000
    ELSE actieId/10000000 END

  • 你正在通过一个只有一个可能值的列进行GROUP BY.因此,GROUP BY是不必要的.希望优化器能够足够聪明地知道这一点,但你永远无法确定.

  • 尝试使用OPTIMIZE FOR提示而不是直接强制索引,这可能会解决您的提示错误

  • Craig Freedman的帖子http://blogs.msdn.com/craigfr/archive/2009/04/28/implied-predicates-and-query-hints.aspx描述了你得到的与提示相关的错误消息的常见原因当使用RECOMPILE时.您可能希望查看该帖子并确保您正在运行SQL Server的最新更新.

  • 我确定你已经完成了这个,但是你可能想要通过我们正在做的事情来构建数据的"洁净室"版本:创建一个新的数据库,在你的问题中使用DDL创建表,然后用数据填充表.如果得到的结果不同,请查看真实表和索引中架构的closeley,看看它们是否不同.

如果这些都不起作用,评论和我可以提出一些更疯狂的想法.:-)

另外,请在您的问题中添加SQL Server的确切版本和更新级别!


Gil*_*ter 6

表中的foreignId是什么数据类型?如果它是int那么你可能会得到一个隐式转换,这会阻止索引搜索.如果表中的数据类型是int,那么将参数重新定义为int,您应该获得此查询的索引搜索(而不是索引扫描).