锁定创建表


19

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

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

    CREATE TABLE MyTable ( ... );

END

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

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

Answers:


18

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

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


6
+1只需确保applock 包装了SELECT检查。否则,您将引入死锁。理想情况下,将应用锁定在S模式下,检查升级到X,但这确实很棘手(至少可以说……)。最安全的选择是获取X,然后进行整个数据库模式部署。这应该是一种罕见的操作(例如,在应用程序启动时),因此X锁应该没有太大关系。
Remus Rusanu 2014年

12

我的建议是尽力尝试/捕获。适当地显式处理重复的案例,例如。忽略它...

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


3
该代码是某种DatabaseLogger,可按需创建其表。没有迁移,没有有趣的事情。但是,您完全正确。我将适当地重构代码。
DR

4
还要考虑到部署/设置完全可以在提升的特权上下文中运行(例如,由管理员执行),但普通操作不是。目前,您需要CREATE TABLE为正常操作申请拨款...
Remus Rusanu 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.