在SQL Server上运行长时间(~10分钟)的存储过程有多危险(性能/资源)?

时间:2015-07-13 17:47:23

标签: sql-server sqlclr

我正在研究在SQL Server上部署一些不占资源但已知的长期存储过程的想法。经过长时间的运行,我在10分钟范围内思考。

它们长时间运行但不占用资源的原因是它们通过C#SQLCLR代码访问外部系统,而这些外部系统的性能导致了性能问题,SQL Server花费了大部分时间它只是在等待外部系统的结果。

我100%意识到做这类事情可能是"不推荐"在SQL Server上,像PowerShell这样的东西会更合适,但是我希望将这个问题限制在实际上对整体SQL Server性能/资源实际上是否有害,如果是这样的话,有多害。

在我的场景中,整体服务器负载不会很高,最多可能有20个其他查询正在运行,其中大多数是针对SQL表的正常查询 - 最多可能有3个用户运行其中一个这样的慢查询。

所以我的问题是:在SQL Server上提供此类查询是否存在任何真正的风险,是否存在与阻塞,连接等相关的问题?

修改

为了便于讨论,我们假设这是在一个4 CPU,8GB RAM盒上运行。

这样做的动机(从业务角度来看)是它有助于使用SQL作为访问多个异构外部系统的通用抽象层,从而消除了对众多终端用户安装各种本地专有客户端软件的依赖,或者了解所涉及的各种系统的模糊调用语法。

我真的希望人们不要投票来关闭这个问题,因为关于一个人是否应该"的哲学观点。这样做与否。能够使用SQL Server实现这一点在经济上是有价值的。但是,如果它实际上在技术上是危险的,那么是否应该描述一些危险的具体细节呢?

编辑2 应主持人的要求,我将提供一些额外的细节,以缩小我所要求的范围。

情景:

我的公司环境中有20个不同的系统。这些系统中的每一个都有一个专门的API,需要安装软件来访问系统,以及正确语法的专业知识,以便查询系统内的数据。每个系统还有一个可以通过C#访问的API。

由于公司内部的每个人都非常熟悉SQL服务器,因此为所有这些系统提供行业标准API在经济上是有利的,这样可以消除在每个客户端桌面上安装特殊软件的要求,以及最终 - 用户需要学习复杂的语法来查询每个不同的系统。在这种情况下,通用API是SQL Server存储过程(实现为C#SQL CLR存储过程,但这对调用者完全透明)。这些过程的接口(参数)简单且文档齐全,并且完全将用户与基础系统调用实现和语法的复杂性隔离开来。

对任何给定底层系统的实际调用的执行时间范围从小于1秒到长达10分钟,具体取决于所调用的特定过程和系统。在这个SQL过程中没有进行额外的昂贵处理,长时间运行只是等待远程系统完成查询并返回结果的过程。

典型的结果集大小为1到50,000行,平均值可能大约为1000行。 非常大型结果集的大小为5 MB。

在任何给定时间,此服务器上最多可能有25个同时执行的查询处于活动状态,其中至少有20个查询正在执行"常规" TSQL存储过程读取驻留在本地数据库中的普通SQL Server表,而其中最多5个可能是这些"特殊的" C#SQL CLR查询访问外部系统。

所有查询都是读取,没有写入,并且没有正在执行的事务性多命令查询。

25个最大并发查询x 5 MB最大结果集大小=最多125 MB"纯数据"在任何给定时间的内存中,加上任何附带的SQL Server"开销"。

这个运行的典型服务器将是一个运行SQL Server 2012的4 CPU,8GB RAM盒。如果我选​​择的话,我有很大的余地来大规模增加这个盒子的功能 - 在这种情况下没有预算限制。

因此,考虑到方案,是否有人知道任何具体技术原因,为什么此实施无效,或合理推测某些技术限制可能出现了?

暂时......我不知道是否有人曾经听过Jeff和Joel在首次开发时回复过的StackoverFlow播客,但这个问题有点让人联想起Joel告诉的轶事他问的是在SQL Server中做了一些不寻常的事情(出于一个非常具体但有效的原因),并且所有答案基本上都是"你不应该这样做!" :)

可能感兴趣的人的参考资料:

https://blog.stackexchange.com/2009/01/podcast-38/

https://stackoverflow.fogbugz.com/default.asp?pg=pgWiki&command=view&ixWikiPage=29025

SQL Server "AFTER INSERT" trigger doesn't see the just-inserted row

同样,我当然意识到这个问题非常不寻常,但如果从纯粹的技术角度考虑,我认为不应该引起争议。

Mods :如果这个额外的细节足以最大限度地减少误解,请告诉我。我真的希望这个问题可以保持开放,因为它是善意的,合法的,并且涉及我认为是一个非常有趣的SQL Server平台功能的边缘案例。

3 个答案:

答案 0 :(得分:3)

鉴于在所有情况下:

  • 只能访问外部系统
  • 访问权限为只读

然后,从概念上讲,SQL Server的稳定性应该没有特定的内在危险。但是,有几件事需要考虑和/或注意:

  • C#API是指您作为项目参考添加的DLL,对吗?此第三方DLL将需要与您的DLL一起加载到SQL Server中。这是事情变得棘手的地方。可以安全地假设DLL将通过网络与其他服务器通信,因此至少需要将其标记为WITH PERMISSION_SET = EXTERNAL_ACCESS

    • 理想情况下,您将使用您(希望)用于程序集的相同.pfx文件对第三方DLL进行签名。如果第三方DLL已经签名,我想你可以重新签名。
    • 此第三方DLL是否正确清理其外部资源?打开外部资源而不妥善处理它们可能会导致问题。 GC可能会清除孤立的外部文件和网络句柄,但我相信当类没有更多引用时会发生这种情况,而且我不确定这是如何受到包含SQLCLR方法的主类在App之前保持活动状态的影响域已卸载。
    • 如果API DLL正在建立HTTP连接,则它将限制为可以对特定URI建立多少连接。虽然这可能不是Windows应用程序或控制台应用程序的问题(因为它们都有自己的应用程序域),但它是SQLCLR中的问题,因为只有一个应用程序域(对于特定的程序集)在所有应用程序中共享会话(即SPID)。默认限制是给定URI的2个连接。如果您有3个会话同时点击相同的API调用,则第3个将被阻止或获得异常。幸运的是,这可以通过ServicePointManager.DefaultConnectionLimit属性进行配置。
    • API DLL是否使用标记有主机保护属性(HPA)的任何方法?您可以通过CREATE ASSEMBLY加载DLL或者如果您的代码调用使用HPA标记的API方法。如果是这样,您需要将API DLL和程序集标记为UNSAFE。这里的风险取决于特定的HPA。例如,通过TimeZoneInfo进行TimeZone转换可能会导致内存泄漏,因此标记为MayLeakOnAbort
    • API DLL是否将值存储在静态类变量中? SQL Server会在您发出CREATE ASSEMBLY而不将程序集设置为UNSAFE时通知DLL是否执行此操作。将程序集设置为UNSAFE将允许它工作,您现在有可能运行访问静态变量的代码的多个会话将经历" odd"行为可能会得到不正确的结果。问题是,所有会话都共享一个App Domain,因此为了使用静态变量需要UNSAFE。唯一可以解决此问题的方法是在任何使用静态变量的API调用周围放置lock。但是,这不是绝对修复,因为静态变量可能用于不同的API调用。存储的值可能对一个特定会话保持使用有意义,但在会话之间不正确。
    • API DLL是否引用了Supported .NET Framework Libraries列表中不存在的任何库?如果是这样,您还需要将它们加载到SQL Server中。但请记住,您无法保证能够将任何不受支持的.NET Framework DLL加载到SQL Server中。 SQL Server仅允许纯程序集(即仅托管代码)。无法加载混合的DLL(托管和非托管C ++代码)。即使不受支持的Framework DLL是纯粹的并且现在可以加载,这可能会在.NET的未来版本中发生变化。事实上,这已经发生过了。风险范围是使用CLR 4.0版的任何Framework更新(这是SQL Server 2012,2014和2016所绑定的)。
    • API DLL是纯的(仅限托管代码)还是混合的(托管和非托管C ++)?如果第三方DLL混合在一起,那么无论如何你都无法加载它。
  • 但是,如果您正在处理Web服务API,那么问题就会大大减少。

    • 您需要使用EXTERNAL_ACCESS
    • 您需要确保正确Dispose()所有可以浸渍的物体。
    • 您需要设置ServicePointManager.DefaultConnectionLimit属性。同样,对于任何特定方法,都有一个在所有会话中共享的App Domain。 DefaultConnectionLimit的默认值为2,这可能不够,因为将在多个会话中访问相同的URI(对于给定的API调用)。
  • 关于SQL Server运行状况的一个问题是SQLCLR代码可能会锁定调度程序,以便在此一个进程完成之前它无法执行任何其他操作。这是因为SQL Server使用抢先式多任务处理,这需要线程让自己处于暂停状态。如果您的SQL Server代码执行查询,那么这不是问题,但如果它只是等待来自外部资源的响应,则存在这种可能性。我没有亲自看到调度程序被SQLCLR进程锁定,但它仍然是可能的,因此最好尝试“好玩”#34;与SQL Server。如果可以对API代码进行async调用,那么您可以使用Timer每10或100毫秒(或类似的东西)调用Thread.Sleep(0);,直到外部过程返回。调用Thread.Sleep(0);是SQLCLR代码如何让SQLOS知道它(即SQLCLR进程)可以被置于保持状态。

    在SQLCLR中执行任何类型的异步工作都需要将程序集标记为UNSAFE。由于已经提到的各种其他原因,您的程序集很可能已被标记为UNSAFE。但即使不是,那么如果这是将它们标记为UNSAFE的唯一原因,那么它仍然值得,特别是这是一个内部项目。

  • 有助于缓解与UNSAFE SQLCLR代码(至少对于主SQL Server进程)相关的稳定性问题的一个选项是在单独的SQL Server实例中隔离它。例如,您可以运行单独的SQL Server Express实例,除了处理这些API调用之外什么都不做。只要指定允许SQL Server Express实例使用的Max Server Memory,内存泄漏就不会影响主SQL Server实例,只会影响Express实例。如果调度程序被长时间运行的外部进程锁定,那么它只会影响Express实例。

    然后,您只需设置一个从主实例到Express实例的链接服务器,以便用户可以处理主实例并加入到那里的任何表等,

  • 最后,从可用性的角度来看,考虑使用这些SQLCLR对象表值函数而不是存储过程。整合他们的结果会容易得多。您不仅可以加入,而且如果有人不想要整个结果集,则很容易添加WHERE子句而不是将所有内容转储到临时表(通过{ {1}})仅删除不需要的行。

答案 1 :(得分:1)

这本身不是问题。根据您描述的内容执行将是可靠的。 “可靠”在某种意义上说,尽管这不是最佳实践,但它可以起作用而不会随机引起问题。

有些问题浮现在脑海:

  1. 如果SQL Server实例出现故障(故障转移,崩溃,重新启动......),则该过程将在中途中止。你必须对此好。 10分钟是一个很长的时间窗口所以机会很高。
  2. 您可以同时运行多少次这样的执行可能是有限的。我不知道具体的限制。如果N = 3,它们不适用。
  3. 长时间运行的事务会导致日志增长,并可能导致令人讨厌的阻塞。
  4. 这些问题都不仅仅因为它们的存在而破坏了实例的稳定性。这些都是完全正常的事情,只是放大了10分钟。

    我不知道为什么有些评论如此激动。 10min查询在数据仓库中很常见,而这些查询使服务器处于100%负载下,这比等待的10分钟差。这里没问题。

    如果您不同意,请发表评论说明原因。当您从SQL Server发出Web服务调用时,请说明完全导致问题。

答案 2 :(得分:-3)

使用正确的工具来完成正确的工作。在您的测试环境中,一切都会顺利运行,但只要您离开该环境并转向生产,您就会被炒掉。

作为主要数据存储的数据库应具有高可用性,而非资源密集型任务的数据库将始终快速增加,具体取决于所进行的并发连接数。在设计系统时请记住最终用户。