LMS*_*LMS 10 c# sql performance stored-procedures azure
总结:
我们有两个相同的数据库,一个在本地服务器上,一个在 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 和 C# 系统中运行 usp_DevelopmentSearch_Select 会生成功能相同的执行计划。
详情:
我们编写了一个非常大的 C# 系统,它访问 SQL Server 数据库。
目前,我们所有的客户都在自己的服务器上本地托管自己的数据库,但是我们正在研究在 Azure 上托管数据库的选项。因此,我建立了一些小型 Azure 测试数据库,解决了这些问题,并启动了一个 Azure 托管系统。
然后我复制了我们客户的一个数据库,以比较本地托管与 Azure 上托管的性能。
实际的客户端数据库在 Azure 上表现得非常糟糕!
第一个屏幕调用存储过程“usp_DevelopmentSearch_Select”
连接到他们服务器上的数据库:-
在 SSMS 中,调用存储过程(如下)大约 1 秒返回值
EXEC usp_DevelopmentSearch_Select @MaxRecord = 100, @SearchType = 'CUR'
Run Code Online (Sandbox Code Playgroud)
在我们的 C# 程序中,调用存储过程在大约 1 秒内返回值
连接到 Azure 上的数据库:-
在SSMS中,调用存储过程大约1秒返回值
在我们的 C# 程序中,调用存储过程在大约17 分钟内返回值!
在 SSMS 中快而在 C# 中慢通常意味着 ARITHABORT,所以我在存储过程开始时打开它:
SET ARITHABORT ON;
Run Code Online (Sandbox Code Playgroud)
这没有任何区别,所以我更新了它以将传递的参数转换为局部变量。
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
Run Code Online (Sandbox Code Playgroud)
仍然没有快乐,所以我得到了两者的执行计划详细信息:-
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')
Run Code Online (Sandbox Code Playgroud)
只是为了检查,我在 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
Run Code Online (Sandbox Code Playgroud)
它肯定使用了上面返回的两个执行计划之一。
因此,检查执行计划的设置
SELECT * FROM sys.dm_exec_plan_attributes (0x05002D00D1A1EA5510E66E783602000001);
SELECT * FROM sys.dm_exec_plan_attributes (0x05002D00D1A1EA55E0FC6E783602000001);
Run Code Online (Sandbox Code Playgroud)
两者的 Set_Options 都是4345,所以他们肯定都在使用 ARITHABORT。
唯一的区别是本地化位:语言和日期格式。Azure 数据库被困在美国,似乎无法改变这一点,而 C# 程序强制它到英国。
我尝试了 C# 程序,但没有将其强制使用 British,但仍然遇到同样的问题。它还使用了完全相同的执行计划,因此显然本地化不会影响它。
因此,我调用了有关执行计划的信息:-
SELECT * FROM sys.dm_exec_query_plan (0x05002D00D1A1EA5510E66E783602000001);
SELECT * FROM sys.dm_exec_query_plan (0x05002D00D1A1EA55E0FC6E783602000001);
Run Code Online (Sandbox Code Playgroud)
保存了它们,并比较了结果:-
最左边的两列显示了整体比较:黄色不同,白色相同。如您所见,这两个执行计划几乎相同,只是在顶部有一些差异。
第一个区别可以在上面的屏幕截图中看到:SSMS(左)窗格中的“StatementCompId”比 C#(右)窗格中的要高一个。Google 不想告诉我StatementCompId是什么是,但鉴于它们是按顺序排列的,我猜这是执行它们的顺序,并且 SSMS 高一,因为调用 SP 的 EXEC 命令算作一。
为方便起见,我已将所有剩余的差异汇总到一个屏幕截图中:-
编译时间和 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>
Run Code Online (Sandbox Code Playgroud)
编辑:一些进展
我尝试了从谷歌搜索中得到的几件事:-
1) 重新编译
我尝试将其添加到存储过程中,没有任何区别
2)选项(优化(@MAXRECORD_Var UNKNOWN,@SEARCHTYPE_Var UNKNOWN))
我尝试将其添加到存储过程中,没有任何区别
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 + ';'
Run Code Online (Sandbox Code Playgroud)
这产生了一组 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;
Run Code Online (Sandbox Code Playgroud)
注意:运行 SET DISABLE_DEF_CNST_CHK OFF; 抛出一个错误,所以我把那个注释掉了。
'DISABLE_DEF_CNST_CHK' is not a recognized SET option.
Run Code Online (Sandbox Code Playgroud)
将此添加到存储过程的开头将时间从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 + ';'
Run Code Online (Sandbox Code Playgroud)
这返回
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;
Run Code Online (Sandbox Code Playgroud)
再次,SET DISABLE_DEF_CNST_CHK ON 行;说这不是您可以设置的选项,所以我将其注释掉。
用这些 SET 值更新了存储过程,然后再试一次。
仍然需要 40 秒,所以没有进一步的进展。
在 SSMS 中运行它仍然需要 1 秒,所以至少它没有破坏它,不是说它有任何帮助,但很高兴知道!
编辑#2:或者不...
昨天的明显进步似乎只是昙花一现:又回到了 17 分钟!(什么都没变)
尝试组合所有三个选项:WITH RECOMPILE、OPTION OPTIMIZE 和显式设置 SET OPTIONS。仍然需要17分钟。
编辑 3:参数嗅探设置
在 SQL Azure 中,您可以从数据库选项屏幕关闭参数嗅探。
并使用检查它们
SELECT * FROM sys.database_scoped_configurations
Run Code Online (Sandbox Code Playgroud)
将此设置为关闭后,分别尝试了两次 SSMS 和 C#。
和以前一样,SSMS 需要 1 秒,C# 仍然需要 15 分钟以上。
当然,鉴于 C# 在连接时强制加载参数到特定状态,它完全有可能覆盖它。
所以,只是说我试过了,我在存储过程中添加了关闭它
ALTER DATABASE SCOPED CONFIGURATION SET PARAMETER_SNIFFING = OFF;
Run Code Online (Sandbox Code Playgroud)
还是 15+ 分钟。
嗯,值得一试!
此外,还有许多新参数可供查找和测试。
编辑 #4:Azure 临时池配置和自动调整
我在暂存池上尝试了几种不同的配置,看看是否有所不同。我没有尝试最糟糕的查询,因为提升 eDTU 需要花费我们的钱,但是我尝试了其他几个查询,每个查询两次(每次都在列表中,所以直接两次都不是同一个)。
从 50 个 eDTU 增加到 100 个 eDTU 有一点不同,所以我猜在我们的测试弹性池中我们使用了所有 50 个,但之后没有任何区别。奇怪的是,高级版在某些地方的表现比标准版差。
然后我将其发布在 Azure MSDN 站点上(当他们最终开始验证我的帐户时),他们建议浏览 Azure 门户上的所有性能选项,看看是否有任何推荐。
它建议了几个我启用的索引,但仅此而已。
然后我将自动调整从“服务器”翻转到“Azure 默认值”
我重新运行了大部分相同的计时测试,只是为了看看它有什么不同。
过去需要 17 分钟的查询现在通常只需要 13 秒,这是一个巨大的改进!好极了!
其余的则是喜忧参半。C 通常更快,大多数仍然花费大约相同的时间,而 E 现在花费几乎两倍的时间(从 14 秒增加了 26 秒)。
结果似乎也比以前有更多的差异,尽管更改 eDTU 大小可能会重置调整。第二次运行通常比第一次好,通常是明显的。
仍然比针对本地服务器上的数据库运行相同的系统慢很多,但至少对于最慢的存储过程来说是一个巨大的改进。
l33*_*33t -4
当您调用 SP 时,C#您应该包含数据库的名称:
[YourDatabaseName].[dbo].[usp_DevelopmentSearch_Select]
在 中SSMS,您的数据库很可能处于活动状态。因此,服务器将知道您正在查询哪个数据库。当在本地服务器上运行时,您很可能只有很少的数据库(也许只有一个?)。因此,您的本地服务器将知道您正在查询哪个数据库。
但是,Azure您很可能有多个数据库,因此它可能需要扫描多个数据库。这将解释您所看到的延迟。
| 归档时间: |
|
| 查看次数: |
1208 次 |
| 最近记录: |