LINQ2SQL 事务的性能

Posted

技术标签:

【中文标题】LINQ2SQL 事务的性能【英文标题】:LINQ2SQL performance with transactions 【发布时间】:2009-05-13 04:04:44 【问题描述】:

我遇到了 LINQ2SQL 和事务的主要性能问题。我的代码使用 IDE 生成的 LINQ2SQL 代码执行以下操作:

运行存储过程检查现有记录 如果记录不存在则创建记录 运行将自己的代码包装在事务中的存储过程

当我在没有事务范围的情况下运行代码时,我每秒获得 20 次迭代。一旦我将代码包装在事务范围内,它就会下降到每秒 3-4 次迭代。我不明白为什么在顶层添加事务会大大降低性能。请帮忙?

带有事务的伪存储过程:

begin transaction
update some_table_1;
insert into some_table_2;
commit transaction;

select some, return, values

没有事务的伪 LINQ 代码:

var db = new SomeDbContext();
var exists = db.RecordExists(some arguments);

if (!exists) 
    var record = new SomeRecord 
    
        // Assign property values
    ;

    db.RecordsTable.InsertOnSubmit(record);
    db.SubmitChanges();

    var result = db.SomeStoredProcWithTransactions();

带有事务的伪 LINQ 代码:

var db = new SomeDbContext();
var exists = db.RecordExists(some arguments);

if (!exists) 
    using (var ts = new TransactionScope()) 
    
        var record = new SomeRecord 
        
            // Assign property values
        ;

        db.RecordsTable.InsertOnSubmit(record);
        db.SubmitChanges();

        var result = db.SomeStoredProcWithTransactions();
        ts.Complete();
    

我知道交易没有升级到 DTC,因为我已禁用 DTC。 SQL Profiler 显示,在启用事务范围的情况下,一些查询需要更长的时间,但我不确定为什么。所涉及的查询非常短暂,并且我已经验证了正在使用的索引。我无法确定为什么添加父事务会导致性能如此下降。

有什么想法吗?

编辑:

我已将问题追溯到最终存储过程中的以下查询:

if exists 
(
    select * from entries where 
        ProfileID = @ProfileID and 
        Created >= @PeriodStart and 
        Created < @PeriodEnd
) set @Exists = 1;

如果我有如下图的with(nolock),问题就消失了。

if exists 
(
    select * from entries with(nolock) where 
        ProfileID = @ProfileID and 
        Created >= @PeriodStart and 
        Created < @PeriodEnd
) set @Exists = 1;

但是,我担心这样做可能会导致问题发生。有什么建议吗?

【问题讨论】:

【参考方案1】:

一旦您收到一笔交易,一件大事就会发生变化 - isolation level。您的数据库是否存在激烈争用?如果是这样:默认情况下,TransactionScope 处于最高的“可序列化”隔离级别,包括读锁、键范围锁等。如果它不能立即获取这些锁,它会在阻塞时减速。您可以通过降低事务的隔离级别(通过构造函数)进行调查。例如(但选择您自己的隔离级别):

using(var tran = new TransactionScope(TransactionScopeOption.Required,
    new TransactionOptions  IsolationLevel = IsolationLevel.Snapshot )) 
    // code
    tran.Complete();

然而,选择一个隔离级别是……棘手的;可序列化是最安全的(因此是默认值)。您还可以使用精细提示(但不能通过 LINQ-to-SQL),例如 NOLOCKUPDLOCK 来帮助控制特定表的锁定。


您还可以调查减速是否是由于试图与 DTC 交谈。启用 DTC 并查看它是否加速。 LTM 很好,但我之前见过对单个数据库的复合操作升级到 DTC...

【讨论】:

我想知道如果存储过程的事务隔离级别与父事务不同会怎样? @Rob - 通常隔离级别是每个事务(基于 spid、ltm 或 dtc)。您的意思是如果 SP 在 proc 内执行“SET TRANSACTION ISOLATION LEVEL”?那将是......有趣;-p 是的到最后一部分 - 如果过程升级隔离级别..我从未尝试过..【参考方案2】:

虽然您使用的是单个数据上下文,但您的代码示例可能会使用多个连接,这会将您的事务升级为分布式事务。

尝试使用显式数据库连接初始化您的数据上下文,或在创建数据上下文后立即调用 db.Connection.Open()。这消除了分布式事务的开销......

【讨论】:

【参考方案3】:

您调用的存储过程是否参与环境(父)事务? - 就是那个问题。

存储过程可能参与了环境事务,这导致了降级。有一个 MSDN article here 讨论它们之间的相互关系。

来自文章:

"当 TransactionScope 对象加入现有的环境事务时,释放范围对象可能不会结束事务,除非范围中止事务。如果环境事务是由根范围创建的,只有在根范围被释放时的,是否在事务上调用 Commit。如果事务是手动创建的,则事务在它被中止或由其创建者提交时结束。"

还有一个关于嵌套事务的严肃文档,看起来它直接适用于本地化 MSDN here。

注意:

“如果在事务处于活动状态时调用 TransProc,则 TransProc 中的嵌套事务在很大程度上被忽略,其 INSERT 语句将根据对外部事务采取的最终操作提交或回滚。”

我认为这可以解释性能差异 - 本质上是维护父事务的成本。 Kristofer 的建议可能有助于减少开销。

【讨论】:

在 TransactionScope 对象范围内执行的存储过程,所以我假设它确实如此。事实上,我几乎可以肯定它确实如此 - 有没有办法告诉它不要(不是我想要的)? 可以查询@@TRANSCOUNT 获取当前的tx count

以上是关于LINQ2SQL 事务的性能的主要内容,如果未能解决你的问题,请参考以下文章

Linq2Sql:即使返回单个记录,也始终执行 LEFT JOIN

性能测试常用指标

SQL Server 2005 事务复制性能

高性能MySQL之事务

LoadRunner中的一些性能名词解释

分布式事务的性能设计