我们试图解决的高级问题是防止来自用户启动的 ETL 过程的重复记录,该过程为它正在导入的每一行调用一个过程(这是一种简化,更改设计不是我的一部分问题)。所以基本上我们有这样的东西(注意该表有大约 20 列):
过程 ImportRow(@p1、@p2、@p3 等)
如果不存在( 从 TargetTable 中
选择 1 ,其中 col1 = @p1 AND col2 = @p2 AND col3 = @p3 等)插入目标表(col1,col2,col3...)
值(@p1,@p2,@p3)
所以很明显我们有一个竞争条件,如果两个人无意中同时运行同一个导入器,我们最终会得到重复的记录。
我们愿意使用SERIALIZABLE
以确保IF NOT EXISTS
将阻止第二个事务进行插入...
过程 ImportRow(@p1、@p2、@p3 等)
设置事务隔离级别可序列化
如果不存在( 从 TargetTable(UPDLOCK)中
选择 1 ,其中 col1 = @p1 AND col2 = @p2 AND col3 = @p3 等)插入目标表(col1,col2,col3...)
值(@p1,@p2,@p3)
...但是,如您所知,如果没有覆盖索引,这将锁定整个表...
...我们不能添加覆盖索引,因为我们有 20 列。
但是前两个参数/列基本上是一个“进口商 id”和一个“截止日期”,所以,真的可以只有 THAT 可序列化。换句话说,防止同一个导入器运行不止一次,即锁定粒度在“整个表”和“逐行”之间。像这样的东西:
过程 ImportRow(@importer、@asOfDate、@p3、@p4、@p5 等)
--lock down the importer
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
SELECT 1
FROM TargetTable (UPDLOCK)
WHERE importer = @importer AND asOfDate = @asOfDate如果不存在( 从 TargetTable (UPDLOCK) 中
选择 1 ,其中 importer = @importer AND asOfDate = @asOfDate AND col3 = @p3 AND col4 = @p4 AND col5 = @p5 等)插入目标表(importer,asOfDate,col3,col4,col5 ...)
值(@importer,@asOfDate,@p3,@p4,@p5)
我们在 importer 和 asOfDate 上有一个覆盖索引,所以只有那些应该被序列化锁定,而表的其余部分应该是自由和清晰的。问题是现在这显然会从第一个SELECT
查询中产生一个缓慢而丑陋的结果集。
那么有没有另一种方法来做到这一点“部分可序列化”?我正在考虑烧一个临时表,但这很浪费。
SELECT 1
INTO #Ignored FROM TargetTable (UPDLOCK)
WHERE importer = @importer AND asOfDate = @asOfDate
我能想到的唯一另一件事是将 importer/asOfDate 放在一个单独的表中并给该对一个 id ('importerDateId'),然后在 TargetTable 中使用该 importerDateId,即对其进行更多规范化,但我'想避免架构更改。