我还没有通过使用存储过程来解决这个问题,但我们已经决定超越SP而只执行普通的'SQL
请参阅下面的扩展表格方案
编辑2:更新了索引(不再使用actieGroep)
NB。 SQL Server 2005 Enterprise 9.00.4035.00
NB2。似乎与http://www.sqlservercentral.com/Forums/Topic781451-338-1.aspx
我在桌子上有两个索引:
我有以下代码:
DECLARE @fid BIGINT
SET @fid = 873926
SELECT foreignId
FROM STAT_Statistieken
WHERE foreignId = @fid
这会按照应有的方式执行;它指向正确的索引,它只是扫描索引。
现在我正在创建一个存储过程:
ALTER PROCEDURE MyProcedure (@fid BIGINT)
AS BEGIN
SELECT foreignId
FROM STAT_Statistieken
WHERE foreignId = @fid
END
运行东西:
EXEC MyProcedure @fid = 873926
现在它正在我的PK索引上运行聚集索引扫描! Wtf还在继续?
所以我将SP更改为
SELECT foreignId
FROM STAT_Statistieken
WITH (INDEX(IX_STAT_Statistieken_2))
WHERE foreignId = @fid
现在它给出了:查询处理器由于此查询中定义的提示而无法生成查询计划。重新提交查询而不指定任何提示,也不使用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]
索引
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]
执行
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))
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
使用索引,如执行计划中所示。当我用这个参数的程序包装它时:
@foreignId bigint,
@maand int, --month
@jaar int --year
并使用_SP_TEMP @foreignId = 873924, @maand = 9, @jaar = 2009
它进行聚簇索引扫描!
答案 0 :(得分: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]
如果这个索引修订版本的问题消失了,那么你知道dagnummer就是问题 - 你甚至可以从索引中删除dagnummer,因为SQL还没有使用它。
此外,修改索引以添加actieId是一个好主意,因为它避开了下面提到的问题。但是在此过程中,您还需要将aantal列留在索引中,以便您的索引对于此查询将是covering index。否则,SQL必须读取您的聚簇索引以获取该列的值。这将减慢您的查询速度,因为查找到聚簇索引非常慢。
[结束编辑]
以下是一些可以帮助您解决此问题的想法,最有可能/最简单的事情:
当我尝试重新使用模式和查询(使用伪生成数据)时,我看到您的PERSISTED计算列actieGroep在运行时重新加载,而不是使用的持久值。这看起来像是SQL Server优化器中的一个错误。由于底层列值actieGroep不存在于覆盖索引IX_STAT_Statistieken_1
索引中(只有计算列存在),如果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的确切版本和更新级别!
答案 1 :(得分:6)
表中的foreignId是什么数据类型?如果它是int那么你可能会得到一个隐式转换,这会阻止索引搜索。如果表中的数据类型是int,则将参数重新定义为int,并且您应该获得此查询的索引搜索(而不是索引扫描)。
答案 2 :(得分:2)
它可能是参数嗅探,所以尝试这样的事情:
ALTER PROCEDURE MyProcedure (@fid BIGINT)
AS BEGIN
DECLARE @fid_sniff BIGINT
SET @fid_sniff=@fid
SELECT foreignId
FROM STAT_Statistieken
WHERE foreignId = @fid_sniff
END
阅读更多anout参数嗅探: http://omnibuzz-sql.blogspot.com/2006/11/parameter-sniffing-stored-procedures.html
答案 3 :(得分:2)
首先,我应该说您创建的索引不是最佳的,因为它们只能用于foreignId
上的过滤。
SQL Server
无法执行SKIP SCAN
,并且您的索引中有secondaryId
,但未使用范围条件进行过滤。
因此foreignId, actieGroep, dagNummer
上的条件不会产生有限数量的范围,也不是完全可以理解的。它只能在foreignID
上过滤,而不能在整个过程中过滤。
现在,到您当前的索引。
我刚创建了你的表,并使用这个脚本填充了随机数据:
DROP TABLE STAT_Statistieken
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]
CREATE NONCLUSTERED INDEX [IX_STAT_Statistieken_1] ON [dbo].[STAT_Statistieken]
(
[foreignId] DESC,
[secondaryId] ASC,
[actieGroep] ASC,
[dagnummer] DESC,
[aantal] ASC --count
)WITH (PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_STAT_Statistieken_2] ON [dbo].[STAT_Statistieken]
(
[foreignId] DESC,
[secondaryId] ASC,
[actieId] ASC,
[dagnummer] DESC,
[aantal] ASC -- count
)WITH (PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [PRIMARY]
;WITH nums AS
(
SELECT 1 AS num
UNION ALL
SELECT num + 1
FROM nums
)
INSERT
INTO STAT_Statistieken (
[foreignId], [datum], [websiteId], [actieId],
[objectSoortId], [aantal])
SELECT TOP 100000
500, GETDATE(), num, num, num, num % 5
FROM nums
UNION ALL
SELECT TOP 100000
num % 1000, GETDATE(), num, num, num, num % 5
FROM nums
OPTION (MAXRECURSION 0)
UPDATE STATISTICS STAT_Statistieken
,无论如何都使用INDEX SEEK
,这很可能意味着问题在于您的数据分发。
我建议你创建一个删除secondaryID
的附加索引,如下所示:
CREATE NONCLUSTERED INDEX [IX_STAT_Statistieken_3] ON [dbo].[STAT_Statistieken]
(
[foreignId] DESC,
[actieGroep] ASC,
[dagnummer] DESC,
[aantal] ASC --count
)
如果您仍想使用当前索引,请运行以下命令:
DBCC SHOW_STATISTICS ('STAT_Statistieken', 'IX_STAT_Statistieken_1')
DBCC SHOW_STATISTICS ('STAT_Statistieken', 'IX_STAT_Statistieken_2')
每个命令将输出三个结果集。
是否可以从每个命令中发布结果集1
和2
,以及结果集3
中的三行,其值为RANGE_HI
,正好在下方且等于{ {1}}?
答案 4 :(得分:1)
如果查询中存在冲突的查询提示,则可以生成您收到的错误消息。
您可以在存储过程之外运行查询,包括提示吗?
另一种思路是,您是否使用不同的参数值测试/运行存储过程?用于创建原始执行计划的参数值可能不适合所有活动。您可能希望考虑重新编译存储过程,以查看在具有不同参数的不同运行之间是否生成了不同的执行计划。
如果您希望确保为每次执行存储过程计算新的查询计划,那么您可以使用WITH RECOMPILE子句。这应该是例外,而不是常态。验证程序的行为,并通过测试生成计划。
答案 5 :(得分:1)
试试这个并告诉我们结果:
DBCC FLUSHPROCINDB:用于清除SQL Server上特定数据库的存储过程高速缓存,而不是整个SQL Server。必须输入要受影响的数据库ID号作为命令的一部分。
您可能希望在测试之前使用此命令,以确保先前的存储过程计划不会对测试结果产生负面影响。
示例:
DECLARE @intDBID INTEGER SET @intDBID =(SELECT dbid FROM master.dbo.sysdatabases WHERE name ='database_name') DBCC FLUSHPROCINDB(@intDBID)
答案 6 :(得分:1)
之前我见过类似的行为,它实际上会采用索引提示并用它做一些更糟糕的事情(带书签查找的未经过滤的索引扫描)。
这四个中的一个应该有所帮助:
1)追加; -T4102; -T4118到SQL Server 2005启动参数(可能适用于SQL 2008)。注意:这会在SQL 2005中带回SQL 2000对IN和NOT IN查询的错误处理。
2)UPDATE STATISTICS [dbo]。[STAT_Statistieken]与FULLSCAN
3)选项(MAXDOP 1) - 有时并行性会导致生成非常愚蠢的查询
4)确保索引在线。
另请注意,如果要在存储过程中创建的表上创建索引,则在编译存储过程查询时该索引不存在,因此不会使用该索引。由于您的表是在dbo中全局创建的,我认为这不是这种情况。
编辑:有时我希望有一个真正的强制计划,你可以直接键入计划,并执行任何可能的计划:类似于数据库的汇编语言。答案 7 :(得分:0)
当您传入参数时,表中有多少行与JOIN相对于表中的总行数? SQL Server选择一个索引,其中包括JOIN返回的匹配行与表中行总数的比率。如果相对于表中的总数返回了大量行,则可以忽略索引作为SQL Server首选项索引,其中匹配行的数量相对于总数较低。
因此,如果您的SELECT和存储过程调用对@fid使用了不同的值,那么您有时可能会使用索引,有时则不会。如果这听起来像你的问题,请看看谷歌的“选择性比率”。
祝你好运!答案 8 :(得分:0)
select AU.*
FROM SYS.Allocation_units AS AU
INNER JOIN SYS.Partitions AS P
ON AU.Container_id = P.Partition_id
WHERE Object_ID = object_id('STAT_Statistieken')
尝试这一点并检查非聚集索引是否有比聚集索引更多的页面(这意味着更方便阅读聚集索引)
答案 9 :(得分:0)
尝试创建这样的索引:
CREATE NONCLUSTERED INDEX [IX_STAT_Statistieken_2] ON [dbo].[STAT_Statistieken]
(
[foreignId] DESC,
[secondaryId] ASC,
[actieId] ASC,
[dagnummer] DESC,
[aantal] ASC -- count
)
INCLUDE (actieGroep);
WITH (PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [PRIMARY]
然后重新创建你的程序