锁定创建表

D.R*_*.R. 19 sql-server sql-server-2012 locking

在另一个应用程序中,我被糟糕的设计震惊了:多个线程EnsureDatabaseSchemaExists()同时执行一个方法,它看起来基本上是这样的:

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'MyTable') AND type = N'U') BEGIN

    CREATE TABLE MyTable ( ... );

END
Run Code Online (Sandbox Code Playgroud)

但是,即使在 SERIALIZABLE 事务中执行,此代码似乎也不是线程安全的(即并行代码尝试多次创建表)。是否有机会强制 SELECT 语句获取阻止另一个线程执行相同 SELECT 语句的锁?

是否有更好的多线程 EnsureSchemaExists() 方法模式?

Pau*_*ite 18

您最好的选择是使用显式包含事务并获取自定义排他锁以使用 sp_getapplock保护整个操作(SELECTCREATE TABLE)。根据设计,系统对象不接受隔离级别请求并以与用户表相同的方式使用锁。

原始代码中的竞争条件是多个线程可以在任何线程到达CREATE TABLE语句之前得出表不存在的结论。

  • +1 只需确保 applock *包装 SELECT 检查*。否则你会引入死锁。理想情况下,可以在 S 模式下锁定应用程序,检查,升级到 X,但这很棘手(至少可以说......)。最安全的选择是获取 X,然后进行整个 DB 模式部署。它应该是一个罕见的操作(例如在应用程序启动时),所以 X 锁应该没有那么重要。 (6认同)

Rem*_*anu 12

我的建议是尽最大努力尝试/捕获。适当地明确处理重复的情况,例如。忽略它...

真正的问题:为什么 DDL 从多个 xact 按需运行?通常升级和迁移是一个严重的问题,在专用的时间窗口中处理......您不希望您的迁移(代码优先?)意外启动,其中一些更新步骤可能需要几个小时在一个大表上(大小为-数据操作...)

  • 还要考虑部署/设置完全可以在提升的权限上下文中运行(例如由管理员),但正常的操作不是。目前,您需要为普通操作授予`CREATE TABLE` 授权... (4认同)
  • 该代码是某种 DatabaseLogger,可按需创建其表。没有移民,没有有趣的生意。然而,你是完全正确的。我将适当地重构代码。 (3认同)