从 .NET 应用程序到 SQL Server 的调用偶尔缓慢
我在 SQL Server 中有一个表,该表是从仍在生产中的遗留系统继承的,该表是根据下面的代码构建的。 我创建了一个 SP 来查询表,如表创建语句下面的代码中所述。 我的问题是,有时,.NET 通过 Enterprise Library 4 和 DataReader 对象调用此 SP 的速度很慢。 SP 通过数据层中的循环结构调用,该循环结构指定进入 SP 的参数以填充用户对象。 值得一提的是,循环结构的每次传递都不会发生缓慢的调用。 通常一天的大部分时间或更长时间都没有问题,然后开始呈现,这使得调试变得非常困难。
该表包含大约 500 万行。 例如,慢速调用平均需要 10 秒,而快速调用平均需要 0 到 10 毫秒。 我在缓慢的调用期间检查了锁定/阻塞事务,但没有发现。 我在数据层创建了一些自定义性能计数器来监控调用时间。 从本质上讲,当性能不佳时,这对于一次调用来说确实很糟糕。 但当它好的时候,它真的很好。 我已经能够在一些不同的开发计算机上重现该问题,但不能在我们的开发和登台数据库服务器上重现该问题,这些服务器当然具有更强大的硬件。 通常,问题可以通过重新启动 SQL Server 服务来解决,但并非总是如此。 表上有我正在查询的字段的索引,但索引比我想要的要多。 但是,我对于删除任何索引或玩具感到犹豫,因为它可能会对遗留系统产生影响。 有没有人以前遇到过这样的问题,或者您有解决方法的建议吗?
CREATE TABLE [dbo].[product_performance_quarterly](
[performance_id] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[product_id] [int] NULL,
[month] [int] NULL,
[year] [int] NULL,
[performance] [decimal](18, 6) NULL,
[gross_or_net] [char](15) NULL,
[vehicle_type] [char](30) NULL,
[quarterly_or_monthly] [char](1) NULL,
[stamp] [datetime] NULL CONSTRAINT [DF_product_performance_quarterly_stamp] DEFAULT (getdate()),
[eA_loaded] [nchar](10) NULL,
[vehicle_type_id] [int] NULL,
[yearmonth] [char](6) NULL,
[gross_or_net_id] [tinyint] NULL,
CONSTRAINT [PK_product_performance_quarterly_4_19_04] PRIMARY KEY CLUSTERED
(
[performance_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[product_performance_quarterly] WITH NOCHECK ADD CONSTRAINT [FK_product_performance_quarterlyProduct_id] FOREIGN KEY([product_id])
REFERENCES [dbo].[products] ([product_id])
GO
ALTER TABLE [dbo].[product_performance_quarterly] CHECK CONSTRAINT [FK_product_performance_quarterlyProduct_id]
CREATE PROCEDURE [eA.Analytics.Calculations].[USP.GetCalculationData]
(
@PRODUCTID INT, --products.product_id
@BEGINYEAR INT, --year to begin retrieving performance data
@BEGINMONTH INT, --month to begin retrieving performance data
@ENDYEAR INT, --year to end retrieving performance data
@ENDMONTH INT, --month to end retrieving performance data
@QUARTERLYORMONTHLY VARCHAR(1), --do you want quarterly or monthly data?
@VEHICLETYPEID INT, --what product vehicle type are you looking for?
@GROSSORNETID INT --are your looking gross of fees data or net of fees data?
)
AS
BEGIN
SET NOCOUNT ON
DECLARE @STARTDATE VARCHAR(6),
@ENDDATE VARCHAR(6),
@vBEGINMONTH VARCHAR(2),
@vENDMONTH VARCHAR(2)
IF LEN(@BEGINMONTH) = 1
SET @vBEGINMONTH = '0' + CAST(@BEGINMONTH AS VARCHAR(1))
ELSE
SET @vBEGINMONTH = @BEGINMONTH
IF LEN(@ENDMONTH) = 1
SET @vENDMONTH = '0' + CAST(@ENDMONTH AS VARCHAR(1))
ELSE
SET @vENDMONTH = @ENDMONTH
SET @STARTDATE = CAST(@BEGINYEAR AS VARCHAR(4)) + @vBEGINMONTH
SET @ENDDATE = CAST(@ENDYEAR AS VARCHAR(4)) + @vENDMONTH
--because null values for gross_or_net_id and vehicle_type_id are represented in
--multiple ways (true null, empty string, or 0) in the PPQ table, need to account for all possible variations if
--a -1 is passed in from the .NET code, which represents an enumerated value that
--indicates that the value(s) should be true null.
IF @VEHICLETYPEID = '-1' AND @GROSSORNETID = '-1'
SELECT
PPQ.YEARMONTH, PPQ.PERFORMANCE
FROM PRODUCT_PERFORMANCE_QUARTERLY PPQ
WITH (NOLOCK)
WHERE
(PPQ.PRODUCT_ID = @PRODUCTID)
AND (PPQ.YEARMONTH BETWEEN @STARTDATE AND @ENDDATE)
AND (PPQ.QUARTERLY_OR_MONTHLY = @QUARTERLYORMONTHLY)
AND (PPQ.VEHICLE_TYPE_ID IS NULL OR PPQ.VEHICLE_TYPE_ID = '0' OR PPQ.VEHICLE_TYPE_ID = '')
AND (PPQ.GROSS_OR_NET_ID IS NULL OR PPQ.GROSS_OR_NET_ID = '0' OR PPQ.GROSS_OR_NET_ID = '')
ORDER BY PPQ.YEARMONTH ASC
IF @VEHICLETYPEID <> '-1' AND @GROSSORNETID <> '-1'
SELECT
PPQ.YEARMONTH, PPQ.PERFORMANCE
FROM PRODUCT_PERFORMANCE_QUARTERLY PPQ
WITH (NOLOCK)
WHERE
(PPQ.PRODUCT_ID = @PRODUCTID)
AND (PPQ.YEARMONTH BETWEEN @STARTDATE AND @ENDDATE)
AND (PPQ.QUARTERLY_OR_MONTHLY = @QUARTERLYORMONTHLY)
AND (PPQ.VEHICLE_TYPE_ID = @VEHICLETYPEID )
AND (PPQ.GROSS_OR_NET_ID = @GROSSORNETID)
ORDER BY PPQ.YEARMONTH ASC
IF @VEHICLETYPEID = '-1' AND @GROSSORNETID <> '-1'
SELECT
PPQ.YEARMONTH, PPQ.PERFORMANCE
FROM PRODUCT_PERFORMANCE_QUARTERLY PPQ
WITH (NOLOCK)
WHERE
(PPQ.PRODUCT_ID = @PRODUCTID)
AND (PPQ.YEARMONTH BETWEEN @STARTDATE AND @ENDDATE)
AND (PPQ.QUARTERLY_OR_MONTHLY = @QUARTERLYORMONTHLY)
AND (PPQ.VEHICLE_TYPE_ID IS NULL OR PPQ.VEHICLE_TYPE_ID = '0' OR PPQ.VEHICLE_TYPE_ID = '')
AND (PPQ.GROSS_OR_NET_ID = @GROSSORNETID)
ORDER BY PPQ.YEARMONTH ASC
IF @VEHICLETYPEID <> '-1' AND @GROSSORNETID = '-1'
SELECT
PPQ.YEARMONTH, PPQ.PERFORMANCE
FROM PRODUCT_PERFORMANCE_QUARTERLY PPQ
WITH (NOLOCK)
WHERE
(PPQ.PRODUCT_ID = @PRODUCTID)
AND (PPQ.YEARMONTH BETWEEN @STARTDATE AND @ENDDATE)
AND (PPQ.QUARTERLY_OR_MONTHLY = @QUARTERLYORMONTHLY)
AND (PPQ.VEHICLE_TYPE_ID = @VEHICLETYPEID)
AND (PPQ.GROSS_OR_NET_ID IS NULL OR PPQ.GROSS_OR_NET_ID = '0' OR PPQ.GROSS_OR_NET_ID = '')
ORDER BY PPQ.YEARMONTH ASC
END
I have a table in SQL Server that I inherited from a legacy system thats still in production that is structured according to the code below. I created a SP to query the table as described in the code below the table create statement. My issue is that, sporadically, calls from .NET to this SP both through the Enterprise Library 4 and through a DataReader object are slow. The SP is called through a loop structure in the Data Layer that specifies the params that go into the SP for the purpose of populating user objects. It's also important to mention that a slow call will not take place on every pass the loop structure. It will generally be fine for most of a day or more, and then start presenting which makes it extremely hard to debug.
The table in question contains about 5 million rows. The calls that are slow, for instance, will take as long as 10 seconds, while the calls that are fast will take 0 to 10 milliseconds on average. I checked for locking/blocking transactions during the slow calls, none were found. I created some custom performance counters in the data layer to monitor call times. Essentially, when performance is bad, it's really bad for that one call. But when it's good, it's really good. I've been able to recreate the issue on a few different developer machines, but not on our development and staging database servers, which of course have beefier hardware. Generally, the problem is resolved through restarting the SQL server services, but not always. There are indexes on the table for the fields I'm querying, but there are more indexes than I would like. However, I'm hesitant to remove any or toy with the indexes due to the impact it may have on the legacy system. Has anyone experienced a problem like this before, or do you have a recommendation to remedy it?
CREATE TABLE [dbo].[product_performance_quarterly](
[performance_id] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[product_id] [int] NULL,
[month] [int] NULL,
[year] [int] NULL,
[performance] [decimal](18, 6) NULL,
[gross_or_net] [char](15) NULL,
[vehicle_type] [char](30) NULL,
[quarterly_or_monthly] [char](1) NULL,
[stamp] [datetime] NULL CONSTRAINT [DF_product_performance_quarterly_stamp] DEFAULT (getdate()),
[eA_loaded] [nchar](10) NULL,
[vehicle_type_id] [int] NULL,
[yearmonth] [char](6) NULL,
[gross_or_net_id] [tinyint] NULL,
CONSTRAINT [PK_product_performance_quarterly_4_19_04] PRIMARY KEY CLUSTERED
(
[performance_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[product_performance_quarterly] WITH NOCHECK ADD CONSTRAINT [FK_product_performance_quarterlyProduct_id] FOREIGN KEY([product_id])
REFERENCES [dbo].[products] ([product_id])
GO
ALTER TABLE [dbo].[product_performance_quarterly] CHECK CONSTRAINT [FK_product_performance_quarterlyProduct_id]
CREATE PROCEDURE [eA.Analytics.Calculations].[USP.GetCalculationData]
(
@PRODUCTID INT, --products.product_id
@BEGINYEAR INT, --year to begin retrieving performance data
@BEGINMONTH INT, --month to begin retrieving performance data
@ENDYEAR INT, --year to end retrieving performance data
@ENDMONTH INT, --month to end retrieving performance data
@QUARTERLYORMONTHLY VARCHAR(1), --do you want quarterly or monthly data?
@VEHICLETYPEID INT, --what product vehicle type are you looking for?
@GROSSORNETID INT --are your looking gross of fees data or net of fees data?
)
AS
BEGIN
SET NOCOUNT ON
DECLARE @STARTDATE VARCHAR(6),
@ENDDATE VARCHAR(6),
@vBEGINMONTH VARCHAR(2),
@vENDMONTH VARCHAR(2)
IF LEN(@BEGINMONTH) = 1
SET @vBEGINMONTH = '0' + CAST(@BEGINMONTH AS VARCHAR(1))
ELSE
SET @vBEGINMONTH = @BEGINMONTH
IF LEN(@ENDMONTH) = 1
SET @vENDMONTH = '0' + CAST(@ENDMONTH AS VARCHAR(1))
ELSE
SET @vENDMONTH = @ENDMONTH
SET @STARTDATE = CAST(@BEGINYEAR AS VARCHAR(4)) + @vBEGINMONTH
SET @ENDDATE = CAST(@ENDYEAR AS VARCHAR(4)) + @vENDMONTH
--because null values for gross_or_net_id and vehicle_type_id are represented in
--multiple ways (true null, empty string, or 0) in the PPQ table, need to account for all possible variations if
--a -1 is passed in from the .NET code, which represents an enumerated value that
--indicates that the value(s) should be true null.
IF @VEHICLETYPEID = '-1' AND @GROSSORNETID = '-1'
SELECT
PPQ.YEARMONTH, PPQ.PERFORMANCE
FROM PRODUCT_PERFORMANCE_QUARTERLY PPQ
WITH (NOLOCK)
WHERE
(PPQ.PRODUCT_ID = @PRODUCTID)
AND (PPQ.YEARMONTH BETWEEN @STARTDATE AND @ENDDATE)
AND (PPQ.QUARTERLY_OR_MONTHLY = @QUARTERLYORMONTHLY)
AND (PPQ.VEHICLE_TYPE_ID IS NULL OR PPQ.VEHICLE_TYPE_ID = '0' OR PPQ.VEHICLE_TYPE_ID = '')
AND (PPQ.GROSS_OR_NET_ID IS NULL OR PPQ.GROSS_OR_NET_ID = '0' OR PPQ.GROSS_OR_NET_ID = '')
ORDER BY PPQ.YEARMONTH ASC
IF @VEHICLETYPEID <> '-1' AND @GROSSORNETID <> '-1'
SELECT
PPQ.YEARMONTH, PPQ.PERFORMANCE
FROM PRODUCT_PERFORMANCE_QUARTERLY PPQ
WITH (NOLOCK)
WHERE
(PPQ.PRODUCT_ID = @PRODUCTID)
AND (PPQ.YEARMONTH BETWEEN @STARTDATE AND @ENDDATE)
AND (PPQ.QUARTERLY_OR_MONTHLY = @QUARTERLYORMONTHLY)
AND (PPQ.VEHICLE_TYPE_ID = @VEHICLETYPEID )
AND (PPQ.GROSS_OR_NET_ID = @GROSSORNETID)
ORDER BY PPQ.YEARMONTH ASC
IF @VEHICLETYPEID = '-1' AND @GROSSORNETID <> '-1'
SELECT
PPQ.YEARMONTH, PPQ.PERFORMANCE
FROM PRODUCT_PERFORMANCE_QUARTERLY PPQ
WITH (NOLOCK)
WHERE
(PPQ.PRODUCT_ID = @PRODUCTID)
AND (PPQ.YEARMONTH BETWEEN @STARTDATE AND @ENDDATE)
AND (PPQ.QUARTERLY_OR_MONTHLY = @QUARTERLYORMONTHLY)
AND (PPQ.VEHICLE_TYPE_ID IS NULL OR PPQ.VEHICLE_TYPE_ID = '0' OR PPQ.VEHICLE_TYPE_ID = '')
AND (PPQ.GROSS_OR_NET_ID = @GROSSORNETID)
ORDER BY PPQ.YEARMONTH ASC
IF @VEHICLETYPEID <> '-1' AND @GROSSORNETID = '-1'
SELECT
PPQ.YEARMONTH, PPQ.PERFORMANCE
FROM PRODUCT_PERFORMANCE_QUARTERLY PPQ
WITH (NOLOCK)
WHERE
(PPQ.PRODUCT_ID = @PRODUCTID)
AND (PPQ.YEARMONTH BETWEEN @STARTDATE AND @ENDDATE)
AND (PPQ.QUARTERLY_OR_MONTHLY = @QUARTERLYORMONTHLY)
AND (PPQ.VEHICLE_TYPE_ID = @VEHICLETYPEID)
AND (PPQ.GROSS_OR_NET_ID IS NULL OR PPQ.GROSS_OR_NET_ID = '0' OR PPQ.GROSS_OR_NET_ID = '')
ORDER BY PPQ.YEARMONTH ASC
END
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
听起来好像另一个查询正在后台运行,该查询已锁定表,而您无辜的查询只是在等待它完成
Sounds like another query is running in the background that has locked the table and your innocent query is simply waiting for it to finish
这是一个奇怪的边缘情况,但我最近遇到了它。
如果查询在应用程序中运行的时间比在 Management Studio 中运行的时间长,您可能需要检查以确保 Arithabort 已关闭。 Management Studio 使用的连接参数与 .NET 使用的连接参数不同。
A strange, edge case but I encountered it recently.
If the queries run longer in the application than they do when run from within Management Studio, you may want to check to make sure that Arithabort is set off. The connection parameters used by Management Studio are different from the ones used by .NET.
看起来这是两件事之一 - 慢速调用的参数在某种程度上与快速调用不同,并且它们也无法使用索引,或者存在某种类型的锁定争用让您陷入困境向上。 您说您在特定进程挂起时检查了阻塞锁,但没有看到任何阻塞锁 - 这表明它是第一个。 但是 - 您确定您的登台服务器(您无法在其上重现此错误)和开发服务器(您可以在其上重现该错误)具有相同的数据库配置吗? 例如,也许在生产中启用了“READ COMMITTED SNAPSHOT”,但在开发中未启用,这将导致读取争用问题在生产中消失。
如果是参数上的差异,我建议使用 SQL Profiler 来观察事务并捕获一些事务 - 一些较慢的事务和一些较快的事务,然后在 Management Studio 窗口中,用参数替换上面 SP 中的变量值,然后按“Control-L”获取执行计划。 这将准确地告诉您 SQL Server 希望如何处理您的查询,您可以比较不同参数组合的执行计划,看看一组参数是否存在差异,并从那里开始优化它。
祝你好运!
It seems like it's one of two things - either the parameters on the slow calls are different in some way than on the fast calls, and they're not able to use the indexes as well, or there's some type of locking contention that's holding you up. You say you've checked for blocking locks while a particular process is hung, and saw none - that would suggest that it's the first one. However - are you sure that your staging server (that you can't reproduce this error on) and the development servers (that you can reproduce it on) have the same database configuration? For example, maybe "READ COMMITTED SNAPSHOT" is enabled in production, but not in development, which would cause read contention issues to disappear in production.
If it's a difference in parameters, I'd suggest using SQL Profiler to watch the transactions and capture a few - some slow ones and some faster ones, and then, in a Management Studio window, replace the variables in that SP above with the parameter values and then get an execution plan by pressing "Control-L". This will tell you exactly how SQL Server expects to process your query, and you can compare the execution plan for different parameter combination to see if there's a difference with one set, and work from there to optimize it.
Good luck!
我已经看到过时的索引会发生这种情况。 这也可能是参数嗅探问题,其中不同的查询计划用于进入存储过程的不同参数。
您应该捕获缓慢调用的参数,并查看每次运行缓慢时它们是否相同。
您还可以尝试运行调整向导并查看它是否推荐任何索引。
您不想担心索引太多,除非您可以证明更新和插入发生得太慢(修改索引所需的时间加上锁定/争用),或者您的磁盘空间不足。
I have seen this happen with indexes that were out of date. It could also be a parameter sniffing problem, where a different query plan is being used for different parameters that come in to the stored procedure.
You should capture the parameters of the slow calls and see if they are the same ones each time it runs slow.
You might also try running the tuning wizard and see if it recommends any indexes.
You don't want to worry about having too many indexes until you can prove that updates and inserts are happening too slow (time needed to modify the index plus locking/contention), or you are running out of disk space for them.