c# - 从C#调用Azure SQL存储过程非常慢

标签 c# sql performance azure stored-procedures

摘要:

我们有两个相同的数据库,一个在本地服务器上,一个在Azure上。

我们有一个C#系统可以访问这些数据库,并调用存储过程。

从C#系统调用到Azure数据库时,存储过程的运行非常非常缓慢。从C#到本地服务器,从SSMS到Azure和本地数据库,它们运行良好。

例如,调用存储过程“usp_DevelopmentSearch_Select”

本地数据库,SSMS:1秒

本地数据库,C#:1秒

Azure数据库,SSMS:1秒

Azure数据库,C#: 17分钟

这是在多个存储过程上发生的,我仅以usp_DevelopmentSearch_Select为例,测试解决方案并跟踪执行计划。

我已经排除了ARITHABORT(通常的嫌疑人),似乎在SSMS中运行usp_DevelopmentSearch_Select并从C#系统运行会生成功能相同的执行计划。

详细信息:

我们编写了一个非常大的C#系统,该系统可以访问SQL Server数据库。

当前,我们所有的客户都在自己的服务器上本地托管自己的数据库,但是我们正在考虑在Azure上托管数据库的选项。因此,我建立了一些小型的Azure测试数据库,消除了类似的问题,并使Azure托管的系统运行起来。

然后,我复制了一个客户端数据库,以比较本地托管与Azure托管的性能。

实际的客户端数据库在Azure上的表现异常糟糕!

第一个屏幕调用存储过程“usp_DevelopmentSearch_Select”

连接到其服务器上的数据库:

在SSMS中,调用存储过程(如下)将在大约1秒钟内返回值

EXEC usp_DevelopmentSearch_Select @MaxRecord = 100, @SearchType = 'CUR'

在我们的C#程序中,调用存储过程将在大约1秒钟内返回值

连接到Azure上的数据库:

在SSMS中,调用存储过程将在大约1秒钟内返回值

在我们的C#程序中,调用存储过程将在 17分钟内返回值!

在SSMS中快而从C#中慢通常意味着ARITHABORT,因此我在存储过程开始时将其打开:
SET ARITHABORT ON; 

没什么区别,所以我更新了它,将传递的参数转换为局部变量。
ALTER PROCEDURE [dbo].[usp_DevelopmentSearch_Select]
     (@MAXRECORD INT,
      @SEARCHTYPE VARCHAR(3))
AS
BEGIN
    SET ARITHABORT ON; 

    DECLARE @MAXRECORD_Var INT = @MAXRECORD
    DECLARE @SEARCHTYPE_Var VARCHAR(3) = @SEARCHTYPE

    ... (Updated all references to @MAXRECORD and @SEARCHTYPE to @MAXRECORD_Var and @SEARCHTYPE_Var)

END

仍然没有喜悦,所以我得到了两个的执行计划详细信息:
select o.object_id, s.plan_handle, h.query_plan 
from sys.objects o 
inner join sys.dm_exec_procedure_stats s on o.object_id = s.object_id
cross apply sys.dm_exec_query_plan(s.plan_handle) h
where o.object_id = object_id('usp_DevelopmentSearch_Select')

只是为了检查,我在C#程序中重新加载了屏幕,并检查了正在运行的查询:
SELECT sqltext.TEXT,
req.session_id,
req.status,
req.command,
req.cpu_time,
req.total_elapsed_time,
req.plan_handle
FROM sys.dm_exec_requests req
CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS sqltext

它肯定是使用上面返回的两个执行计划之一。

因此,请检查执行计划的设置
SELECT * FROM sys.dm_exec_plan_attributes (0x05002D00D1A1EA5510E66E783602000001);
SELECT * FROM sys.dm_exec_plan_attributes (0x05002D00D1A1EA55E0FC6E783602000001);

Compare Settings

两者的Set_Options均为 4345 ,因此它们肯定都是使用ARITHABORT。

唯一的区别是本地化位:语言和日期格式。 Azure数据库陷于美国,似乎无法改变,而C#程序将其强制为英国。

我尝试了C#程序,但没有将其强制为英语,但仍然遇到相同的问题。它还使用了完全相同的执行计划,因此显然本地化不会对此产生影响。

所以,我调出了执行计划的信息:
SELECT * FROM sys.dm_exec_query_plan (0x05002D00D1A1EA5510E66E783602000001);
SELECT * FROM sys.dm_exec_query_plan (0x05002D00D1A1EA55E0FC6E783602000001);

保存它们两个,并比较结果:

Compare Execution Plans

最左边的两列显示了总体比较:黄色不同,白色相同。
如您所见,两个执行计划几乎完全相同,只是顶部有一些差异。

可以在上面的屏幕截图中看到第一个区别:SSMS(左) Pane 中的“StatementCompId”比C#(右) Pane 中的高。 Google不想告诉我 StatementCompId 是什么,但是考虑到它们的顺序,我想这是它们的顺序,而且SSMS更高,因为调用SP的EXEC命令算作一个。

为了简便起见,我将所有剩余的差异汇总到一个屏幕截图中:-

Compare Execution Plans

编译时间和CPU使用率,可用内存以及更多的“StatementCompId”

因此,两个执行计划在功能上是相同的,但设置相同(除了本地化似乎没有效果)。

那么,为什么从C#调用Azure SP大约需要17分钟,而从SSMS调用Azure SP或从本地托管数据库调用本地SP大约需要1秒呢?

存储过程本身只是一个SELECT FROM,对其他表有一些LEFT JOIN,没有什么花哨的地方,而且它在本地托管的数据库上从来没有给我们带来任何麻烦。
SELECT TOP (@MAXRECORD_Var) <FieldList>
FROM (
    SELECT DISTINCT <FieldList>
    FROM <TableName> WITH (NOLOCK)
    LEFT JOIN <TableName> WITH (NOLOCK) ON <Link>
    LEFT JOIN <TableName> WITH (NOLOCK) ON <Link>
    LEFT JOIN <TableName> WITH (NOLOCK) ON <Link>
    LEFT JOIN <TableName> WITH (NOLOCK) ON <Link>
    LEFT JOIN <TableName> WITH (NOLOCK) ON <Link>
    WHERE (
        <Conditions>
    ) AS Base
ORDER BY <FieldName>

编辑:进度

我尝试了Google搜集的几项内容:

1)有建议

我尝试将其添加到存储过程中,没有任何区别

2)选项(为(@MAXRECORD_Var未知,@SEARCHTYPE_Var未知)优化)

我尝试将其添加到存储过程中,没有任何区别

3)明确设置所有选项

这有很大的区别(但仍然太小了)!

我写了一个查询告诉我当前的选择
DECLARE @options INT
SELECT @options = @@OPTIONS
PRINT @options
PRINT 'SET DISABLE_DEF_CNST_CHK ' + CASE WHEN ( (1 & @options) = 1 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET IMPLICIT_TRANSACTIONS ' + CASE WHEN ( (2 & @options) = 2 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET CURSOR_CLOSE_ON_COMMIT ' + CASE WHEN ( (4 & @options) = 4 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ANSI_WARNINGS ' + CASE WHEN ( (8 & @options) = 8 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ANSI_PADDING ' + CASE WHEN ( (16 & @options) = 16 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ANSI_NULLS ' + CASE WHEN ( (32 & @options) = 32 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ARITHABORT ' + CASE WHEN ( (64 & @options) = 64 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ARITHIGNORE ' + CASE WHEN ( (128 & @options) = 128 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET QUOTED_IDENTIFIER ' + CASE WHEN ( (256 & @options) = 256 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET NOCOUNT ' + CASE WHEN ( (512 & @options) = 512 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ANSI_NULL_DFLT_ON ' + CASE WHEN ( (1024 & @options) = 1024 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ANSI_NULL_DFLT_OFF ' + CASE WHEN ( (2048 & @options) = 2048 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN ( (4096 & @options) = 4096 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET NUMERIC_ROUNDABORT ' + CASE WHEN ( (8192 & @options) = 8192 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET XACT_ABORT ' + CASE WHEN ( (16384 & @options) = 16384 ) THEN 'ON' ELSE 'OFF' END + ';'

这产生了一组SET语句,以及当前的Options值
5496
SET DISABLE_DEF_CNST_CHK OFF;
SET IMPLICIT_TRANSACTIONS OFF;
SET CURSOR_CLOSE_ON_COMMIT OFF;
SET ANSI_WARNINGS ON;
SET ANSI_PADDING ON;
SET ANSI_NULLS ON;
SET ARITHABORT ON;
SET ARITHIGNORE OFF;
SET QUOTED_IDENTIFIER ON;
SET NOCOUNT OFF;
SET ANSI_NULL_DFLT_ON ON;
SET ANSI_NULL_DFLT_OFF OFF;
SET CONCAT_NULL_YIELDS_NULL ON;
SET NUMERIC_ROUNDABORT OFF;
SET XACT_ABORT OFF;

注意:运行SET DISABLE_DEF_CNST_CHK OFF;引发错误,因此我将其注释掉。
'DISABLE_DEF_CNST_CHK' is not a recognized SET option.

将其添加到存储过程的开头使时间从 17分钟减少到 40秒

在SSMS中运行仍需要1秒钟以上的时间,仍然不足以使用,但仍然取得了进展。

但是,我注意到它返回的Options值( 5496 )与我从上面的执行计划详细信息中获得的值( 4345 )不同,还有一些设置与该数据库的设置不同。

因此,我重新将查询硬编码为4345
DECLARE @options INT
SELECT @options = 4345 --@@OPTIONS
PRINT @options
PRINT 'SET DISABLE_DEF_CNST_CHK ' + CASE WHEN ( (1 & @options) = 1 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET IMPLICIT_TRANSACTIONS ' + CASE WHEN ( (2 & @options) = 2 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET CURSOR_CLOSE_ON_COMMIT ' + CASE WHEN ( (4 & @options) = 4 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ANSI_WARNINGS ' + CASE WHEN ( (8 & @options) = 8 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ANSI_PADDING ' + CASE WHEN ( (16 & @options) = 16 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ANSI_NULLS ' + CASE WHEN ( (32 & @options) = 32 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ARITHABORT ' + CASE WHEN ( (64 & @options) = 64 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ARITHIGNORE ' + CASE WHEN ( (128 & @options) = 128 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET QUOTED_IDENTIFIER ' + CASE WHEN ( (256 & @options) = 256 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET NOCOUNT ' + CASE WHEN ( (512 & @options) = 512 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ANSI_NULL_DFLT_ON ' + CASE WHEN ( (1024 & @options) = 1024 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET ANSI_NULL_DFLT_OFF ' + CASE WHEN ( (2048 & @options) = 2048 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET CONCAT_NULL_YIELDS_NULL ' + CASE WHEN ( (4096 & @options) = 4096 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET NUMERIC_ROUNDABORT ' + CASE WHEN ( (8192 & @options) = 8192 ) THEN 'ON' ELSE 'OFF' END + ';'
PRINT 'SET XACT_ABORT ' + CASE WHEN ( (16384 & @options) = 16384 ) THEN 'ON' ELSE 'OFF' END + ';'

这回来了
4345
SET DISABLE_DEF_CNST_CHK ON;
SET IMPLICIT_TRANSACTIONS OFF;
SET CURSOR_CLOSE_ON_COMMIT OFF;
SET ANSI_WARNINGS ON;
SET ANSI_PADDING ON;
SET ANSI_NULLS ON;
SET ARITHABORT ON;
SET ARITHIGNORE ON;
SET QUOTED_IDENTIFIER OFF;
SET NOCOUNT OFF;
SET ANSI_NULL_DFLT_ON OFF;
SET ANSI_NULL_DFLT_OFF OFF;
SET CONCAT_NULL_YIELDS_NULL ON;
SET NUMERIC_ROUNDABORT OFF;
SET XACT_ABORT OFF;

再次,行 SET DISABLE_DEF_CNST_CHK ON; 说这不是您可以设置的选项,因此我将其注释掉。

使用这些SET值更新了存储过程,然后重试。

它仍然需要40秒,因此没有进一步的进展。

在SSMS中运行它仍然需要1秒钟,因此至少它并没有破坏它,这不是什么帮助,但很高兴知道!

编辑#2 :否...

似乎昨天的明显进展是短暂的:又回到了17分钟! (没有任何改变)

尝试结合所有三个选项:WITH RECOMPILE,OPTION OPTIMIZE和显式设置SET OPTIONS。仍然需要17分钟。

编辑3 :参数嗅探设置

在SQL Azure中,可以从数据库选项屏幕中关闭“参数嗅探”。

enter image description here

并使用检查它们
SELECT * FROM sys.database_scoped_configurations

enter image description here

将其设置为OFF后,​​分别尝试两次SSMS和C#。

和以前一样,SSMS需要1秒钟,C#仍需要15分钟以上。

当然,给定C#在连接时将参数加载强制为特定状态,则很有可能覆盖它。

因此,只是说我尝试过,我将其关闭到存储过程中
ALTER DATABASE SCOPED CONFIGURATION SET PARAMETER_SNIFFING = OFF;

还是15分钟以上。

嗯,值得一试!

另外,还有许多新参数需要查找和测试。

编辑#4 :Azure临时池配置和自动调整

我在暂存池上尝试了几种不同的配置,以查看是否有所作为。我没有尝试最糟糕的查询,因为这花了我们很多钱才能升级eDTU,但我尝试了其他几次,每一次两次(每次都从列表中向下浏览,所以一次不重复两次)。

Timing Tests

从50个eDTU到100个eDTU有所不同,所以我猜在我们的测试 flex 池中,我们使用了全部50个eDTU,但是之后没有任何区别。奇怪的是,在某些地方,高级版的性能比标准版差。

然后,我将此内容发布在Azure MSDN站点上(当他们最终了解“验证我的帐户”时),他们建议仔细检查Azure门户上的所有“性能”选项,看看是否有任何建议。

Azure Performance Options

它建议了几个启用了索引的索引,仅此而已。

然后,我将“自动调整”从“服务器”切换为“Azure默认值”

Azure Defaults

我重新运行了大多数相同的时序测试,只是想看看它带来了什么变化。

Timing Tests After

原来耗时17分钟的查询现在通常只需13秒,这是一个很大的改进!好极了!

其余的是混合袋。 C通常更快,大多数仍然在同一时间,而E现在花费的时间几乎是原来的两倍(从14s增加到26s)。

尽管更改eDTU大小可能会重置调整,但结果似乎也比以前有更多的变化。第二轮通常比第一轮更好,通常也是如此。

仍然比在本地服务器上对数据库运行同一系统慢得多,但是至少对于最慢的存储过程而言,这是一个巨大的改进。

最佳答案

C#调用SP时,应包括数据库名称:[YourDatabaseName].[dbo].[usp_DevelopmentSearch_Select]SSMS中,您的数据库很可能处于 Activity 状态。因此,服务器将知道您正在查询哪个数据库。在本地服务器上运行时,您很可能只有很少的数据库(也许只有一个?)。因此,您的本地服务器将知道您正在查询哪个数据库。
但是,在Azure中,您可能有多个数据库,因此可能需要扫描多个数据库。这将解释您所看到的延迟。

关于c# - 从C#调用Azure SQL存储过程非常慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59036323/

相关文章:

c# - 如何在运行时向表中添加单元格

c# - Regex Match.Value 返回整个值,而不是匹配的组

MySQL 查询获取的数据与预期不同

c# - 列表的性能问题

c# - 为什么添加局部变量会使 .NET 代码变慢

c# - 你能锁定通用词典吗?

c# - 登录前隐藏菜单,登录后显示

SQL - 全名中的名字和姓氏

mysql - 产品/选项/Exras 表的审核日志记录?

sql - postgres 综合性能