在同一个SQL Server实例上并行执行CREATE DATABASE语句会导致错误,但在不同的SQL Server实例上不会。

8
我正在使用最新版本的Entity Framework在我的应用程序中(但我不认为EF是问题所在,只是说明我们正在使用的ORM),并且有这个多租户架构。 我正在进行一些压力测试,使用C#构建,其中创建X个任务并行运行以执行某些操作。 在整个过程的开始阶段,它将为每个任务(在这种情况下是每个租户)创建一个新数据库,然后继续处理大部分操作。 但是在某些任务上,在尝试创建新数据库的代码的确切部分会抛出2个SQL异常。
异常#1:
无法在数据库“model”上获得独占锁定。请稍后重试。CREATE DATABASE失败。列出了一些文件名无法创建。检查相关错误。
异常#2:
超时已过期。操作完成之前超时时间已过或服务器未响应。
这可能是两者之一,并在我的代码同一行抛出异常(当EF创建数据库时)。显然,在SQL Server中,创建数据库是逐个完成并锁定“model”数据库(请参见here),因此某些等待的任务会超时或发生“model”错误。 这些测试是在我们的开发 SQL Server 2014实例(12.0.4213)上进行的,如果我执行100个并行任务,则必定会在某些任务或甚至几乎一半的任务中抛出错误。但是最令人不安的是,当我在另一个 SQL服务器实例(12.0.2000)上测试时,它完全没有抛出任何错误,并且可以完成我执行的所有任务(即使是1000个并行任务!)。到目前为止,我尝试过的解决方案都没有起作用。
  • 将EF中对象上下文的超时时间更改为无限
  • 尝试在连接字符串上添加更长或无限的超时时间
  • 尝试在EF上添加重试策略并将其设为更长且更频繁运行
  • 目前正在尝试安装与我们的Dev服务器类似环境的虚拟机(使用Windows Server 2014 R2),并在特定版本的SQL Server上进行测试,以尝试查看版本是否与此有关(是的,我很绝望 :))

无论如何,这里有一个简单的C#控制台应用程序,您可以下载并尝试复制该问题。此测试应用程序将执行您输入的N个任务,并仅创建数据库,然后立即进行清理。

2个回答

3

有两个观察结果:

  1. 由于潜在问题与并发性以及对“资源”的访问有关,该关键点仅允许单个但不是并发的访问者,因此在负载下执行高度并发的场景时,在两台不同的计算机上可能会得到不同的结果,这并不奇怪。此外,可能涉及SQL Server引擎的差异。所有这些都是尝试找出和调试并发问题的常规过程,特别是涉及到具有自己非常强烈的并发概念的引擎。

  2. 与其试图使某些东西工作或完全解释情况相反,当事情从经验上来看不起作用时,为什么不通过设计更干净地处理问题来改变方法?

    • 一种选择:承认SQL Server需要在模型数据库上具有独占锁定的现实,通过某种并发同步机制(例如System.Threading.Monitor)来调节访问,这听起来正是此处所发生的情况,并且它将允许您控制在超时时发生的情况,超时时间由您选择。这将有助于防止可能发生在SQL Server端的锁定类型场景,这将是当前“超时”症状的解释(虽然压力负载可能是唯一的解释)。

    • 另一个选择:看看是否可以以不需要同步的方式设计。达到一个点,您从不同时请求多个数据库创建。某种创建请求队列-保证只有一个线程服务该队列-并且请求任务对创建结果进行异步/等待模式。

无论哪种方式,都会出现在压力测试下变得缓慢并导致失败的情况。关键问题是:

  • 您的设计能否处理最坏情况负载的多个倍数,并仍然显示可接受的性能?
  • 如果发生故障,您对故障的响应是否以您设计的方式“受控”?

谢谢DWright。我也考虑过使用队列操作并创建一个线程来创建数据库,但我采用的解决方案是创建一个“模板”数据库。基本上,这是一个空的数据库,EF在应用程序启动时创建,然后每个任务(或租户)都将模板数据库恢复并将相应的逻辑数据库文件重命名和移动为自己的。从那以后,它一直很好地运行着。 - Randolph
听起来是个不错的解决方案! - DWright

1

也许您在开发和本地实例上为SSDT(DacFx Deploy)设置了不同的LockTimeoutSecondsQueryTimeoutSeconds,用于部署数据库。

例如,LockTimeoutSeconds用于设置lock_timeout。如果这里设置的数字很小,那么这就是造成以下原因的原因:

无法在数据库“model”上获得独占锁定。稍后重试操作。CREATE DATABASE失败。列出的一些文件名无法创建。请检查相关错误。

您可以使用下面的查询来确定SSDT设置了什么超时时间。

select session_id, lock_timeout, * from sys.dm_exec_sessions where login_name = 'username'

为了增加默认超时时间,请找到正在部署数据库的用户的标识符,如下所示:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
然后找到下面的注册表键:
HKEY_USERS\您的用户标识符\Microsoft\VisualStudio\您的版本\SQLDB\Database 并更改LockTimeoutSeconds和QueryTimeoutSeconds的值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接