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),例如 NOLOCK
和 UPDLOCK
来帮助控制特定表的锁定。
您还可以调查减速是否是由于试图与 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 事务的性能的主要内容,如果未能解决你的问题,请参考以下文章