我应该使用哪个隔离级别进行以下insert-if-not-present事务?

时间:2010-05-20 21:12:19

标签: .net sql linq-to-sql transactions isolation-level

我编写了一个基本执行ETL任务的linq-to-sql程序,我注意到许多并行化将提高其性能的地方。但是,当两个线程执行以下任务(伪代码)时,我担心会阻止单一约束违规。

Record CreateRecord(string recordText)
{
    using (MyDataContext database = GetDatabase())
    {
        Record existingRecord = database.MyTable.FirstOrDefault(record.KeyPredicate());
        if(existingRecord == null)
        {
            existingRecord = CreateRecord(recordText);
            database.MyTable.InsertOnSubmit(existingRecord);
        }

        database.SubmitChanges();
        return existingRecord;
    }
}

通常,此代码执行SELECT语句以测试记录存在,如果记录不存在,则执行INSERT语句。它由隐式事务封装。

当两个线程为同一recordText实例运行此代码时,我想阻止它们同时确定该记录不存在,从而试图创建相同的记录。隔离级别和显式事务将很好地工作,除了我不确定我应该使用哪个隔离级别 - Serializable应该工作,但似乎太严格。有更好的选择吗?

1 个答案:

答案 0 :(得分:1)

我使用类似于下面显示的SQL来避免这种情况。 UPDLOCK指定在事务完成并且HOLDLOCK等效于SERIALIZABLE之前采取并保持更新锁。 SERIALIZABLE通过在事务完成之前保留共享锁来限制共享锁,而不是在不再需要所需的表或数据页时立即释放共享锁,无论事务是否已完成。执行扫描的语义与在SERIALIZABLE隔离级别运行的事务的语义相同。 HOLDLOCK仅适用于为其指定的表或视图,并且仅适用于由其使用的语句定义的事务的持续时间。HOLDLOCK无法在SELECT中使用包含FOR BROWSE选项的语句。

declare @LocationID          int
declare @LocationName        nvarchar (50)

/* fill in LocationID and LocationName appropriately */

INSERT dbo.Location
(LocationID, LocationName)
SELECT @LocationID, @LocationName
WHERE NOT EXISTS (
   SELECT L.*
   FROM dbo.Location L WITH (UPDLOCK, HOLDLOCK)
   WHERE L.LocationID = @LocationID)

根据this question的答案,Serializable似乎是要走的路。