【问题标题】:SQL Server : is there any performance penalty for wrapping a SELECT query in a transaction?SQL Server:在事务中包装 SELECT 查询是否有任何性能损失?
【发布时间】:2016-10-22 23:01:30
【问题描述】:

作为学习练习,在尝试使用任何 ORM(如 EF)之前,我想使用 ADO.NET 和存储过程构建一个个人项目。

因为我不希望我的代码随着时间的推移变得一团糟,所以我想使用一些模式,比如存储库和 UoW 模式。

除了事务处理之外,我几乎把所有事情都弄清楚了。

为了以某种方式“模拟”一个 UoW,我使用了 @jgauffin 提供的 this class,但是阻止我使用该类的是,每次创建该类的新实例 (AdoNetUnitOfWork) 时,你都会自动开始一个事务,很多情况下你只需要读取数据。

在这方面,这是我在我一直在阅读的一本 SQL 书籍中发现的:

在事务中执行 SELECT 语句可以在引用的表上创建锁,从而阻止其他用户或会话执行工作或读取数据

这是AdoNetUnitOfWork 类:

public class AdoNetUnitOfWork : IUnitOfWork
{
    public AdoNetUnitOfWork(IDbConnection connection, bool ownsConnection)
    {
        _connection = connection;
        _ownsConnection=ownsConnection;
        _transaction = connection.BeginTransaction();
    }

    public IDbCommand CreateCommand()
    {
        var command = _connection.CreateCommand();
        command.Transaction = _transaction;
        return command;
    }

    public void SaveChanges()
    {
        if (_transaction == null)
            throw new InvalidOperationException("Transaction have already been commited. Check your transaction handling.");

        _transaction.Commit();
        _transaction = null;
    }

    public void Dispose()
    {
        if (_transaction != null)
        {
            _transaction.Rollback();
            _transaction = null;
        }

        if (_connection != null && _ownsConnection)
        {
            _connection.Close();
            _connection = null;
        }
    }
}

这就是我想在我的存储库中使用 UoW 的方式:

public DomainTable Get(int id)
{
    DomainTable table;

    using (var commandTable = _unitOfWork.CreateCommand())
    {
        commandTable.CommandType = CommandType.StoredProcedure;
        //This stored procedure contains just a simple SELECT statement
        commandTable.CommandText = "up_DomainTable_GetById";

        commandTable.Parameters.Add(commandTable.CreateParameter("@pId", id));

        table = ToList(commandTable).FirstOrDefault();
    }

    return table;
}

我知道我可以稍微调整一下这段代码,以便事务是可选的,但是因为我试图让这段代码尽可能独立于平台,而且据我所知,在其他持久性框架(如 EF)中你没有要手动管理事务,问题是,我是否会按原样使用此类,即始终创建事务?

【问题讨论】:

标签: c# sql-server ado.net unit-of-work sqltransaction


【解决方案1】:

这一切都取决于transaction isolation level。使用默认隔离级别(即已提交读取),如果将 SELECT 包装在事务中,则不会出现性能损失。如果一个事务尚未启动,SQL Server 会在内部将语句包装在事务中,因此您的代码应该表现得几乎相同。

但是,我必须问你为什么不使用内置的.Net TransactionScope?这样,您的代码将与其他库和框架更好地交互,因为TransactionScope 被普遍使用。如果您决定切换到此,我必须警告您,默认情况下,TransactionScope 使用 SERIALIZABLE 隔离级别,这确实会导致性能下降,请参阅using new TransactionScope() Considered Harmful

【讨论】:

  • 谢谢@Remus。我还看到在其他问题中提到,默认情况下 SQL 在内部为每个语句创建一个事务,但我无法找到任何证实这一点的来源。关于你问我为什么没有使用TransactionScope,好吧,因为在我读数据库和 c# 的那段时间里,这是我第一次听说它(我是一个前端开发人员尝试进入后端世界),但这听起来很有趣。
  • 我知道每个项目都是不同的,但根据您的经验,哪种隔离级别已被证明对您最有用,或者您在开始新项目时通常默认使用的隔离级别?
  • 除非我有特定的理由要更改它,否则我会将其保留为已提交读取。
  • 另外,如果您刚刚开始研究后端 C# 应用服务器开发,那么您应该考虑返回 IQueryable 而不是列表。 IQueryable 从存储库返回后可以进一步细化,实际数据库查询延迟到实际需要数据时。见softwareengineering.stackexchange.com/questions/192044/…asp.net/mvc/overview/older-versions/…
  • 谢谢@Remus。我会将其保留为已提交阅读,并感谢有关使用 IQueryable 的提示。在我的存储库中,我没有返回 List,而是返回 IEnumerable。当我开始使用像 EF 这样的 ORM 时,IIRC IQueryable 将证明非常有用,因为它提供了延迟执行。您在我的存储库中看到的 ToList 是扩展方法的一部分,该方法使用反射将查询中的每一列映射到我的类中的属性,至少在可能的情况下。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-23
  • 1970-01-01
  • 2010-10-06
相关资源
最近更新 更多