我遇到LINQ2SQL和事务的主要性能问题。我的代码使用IDE生成的LINQ2SQL代码执行以下操作:
对现有记录运行存储过程检查 如果记录不存在,请创建该记录 运行在事务中包装自己的代码的存储过程
当我运行没有事务范围的代码时,我每秒得到20次迭代。一旦我将代码包装在事务范围中,它就会降低到每秒3-4次迭代。我不明白为什么在顶级添加事务会降低性能。请帮帮忙?
Psuedo存储过程与事务:
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事件探查器显示启用事务处理器时,有几个查询需要更长时间,但我不确定原因。涉及的查询非常短暂,我已经使用了已验证的索引。我无法确定添加父事务会导致性能下降的原因。
有什么想法吗?
修改
我已将问题跟踪到最终存储过程中的以下查询:
if exists
(
select * from entries where
ProfileID = @ProfileID and
Created >= @PeriodStart and
Created < @PeriodEnd
) set @Exists = 1;
如果我有(nolock)如下所示,问题就会消失。
if exists
(
select * from entries with(nolock) where
ProfileID = @ProfileID and
Created >= @PeriodStart and
Created < @PeriodEnd
) set @Exists = 1;
然而,我担心这样做可能会导致问题。有什么建议吗?
答案 0 :(得分:3)
一旦收到交易,一件大事就会发生变化 - isolation level。您的数据库是否存在争议?如果是这样的话:默认情况下,TransactionScope
处于最高“可序列化”隔离级别,包括读锁,键范围锁等。如果它无法立即获取这些锁,它会在阻塞时减速。您可以通过降低事务的隔离级别(通过构造函数)进行调查。例如(但选择你自己的隔离级别):
using(var tran = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot })) {
// code
tran.Complete();
}
然而,挑选隔离级别是......棘手的; serializable是最安全的(因此是默认值)。您还可以使用粒度提示(但不能通过LINQ-to-SQL),例如NOLOCK
和UPDLOCK
来帮助控制特定表的锁定。
您还可以调查减速是否是由于尝试与DTC对话造成的。启用DTC并查看它是否加速。 LTM很好,但我看到单个数据库的复合操作在之前升级到DTC ......
答案 1 :(得分:0)
您调用的存储过程是否参与环境(父)事务? - 就是那个问题。
存储过程可能会参与环境事务,这会导致降级。有MSDN article here讨论它们如何相互关联。
来自文章:
“当TransactionScope对象加入现有环境事务时,除非范围中止事务,否则处理范围对象可能不会结束事务。如果环境事务是由根范围创建的,则仅在根范围被处置时是否会在事务中调用Commit。如果事务是手动创建的,则事务在中止或由其创建者提交时结束。“
还有一个关于嵌套交易的认真文件,看起来直接适用于MSDN here的本地化。
注意:
“如果在事务处于活动状态时调用TransProc,则TransProc中的嵌套事务在很大程度上被忽略,并且根据对外部事务采取的最终操作提交或回滚其INSERT语句。”
我认为这解释了性能的差异 - 它本质上是维护父事务的成本。 Kristofer的建议可能有助于减少开销。
答案 2 :(得分:0)
虽然您使用的是单个datacontext,但您的代码示例可能会使用多个连接,并且会将您的事务升级为分布式事务。
尝试使用显式数据库连接初始化datacontext,或在创建datacontext后立即调用db.Connection.Open()。这消除了分布式事务的开销......