【问题标题】:Fastest method for SQL Server inserts, updates, selectsSQL Server 插入、更新、选择的最快方法
【发布时间】:2011-02-21 04:33:11
【问题描述】:

我使用 SP,这不是 SP 与代码隐藏的“构建您的 SQL 命令”问题。我正在为处理许多小事务的后端应用程序寻找一种高吞吐量的方法。我使用 SQLDataReader 进行大部分返回,因为 forward 仅在大多数情况下对我有效。

我已经看到它做了很多方法,并且我自己使用了其中的大部分。

  1. 定义和接受存储过程参数作为参数本身并使用 cmd.Parameters.Add 构建的方法(指定或不指定 DB 值类型和/或长度)

  2. 将您的 SP 参数及其值组装到一个数组或哈希表中,然后传递给一个更抽象的方法来解析集合,然后运行 ​​cmd.Parameters.Add

  3. 表示表的类,根据需要初始化类,设置表示表字段的公共属性,调用保存、加载等方法

我敢肯定还有其他我见过但现在也想不起来的人。我愿意接受所有建议。

【问题讨论】:

    标签: c# asp.net sql-server ado.net


    【解决方案1】:

    此答案主要关注“选择”与更新/创建/删除操作。我认为一次更新超过一条或几条记录的情况很少见,因此我也认为“选择”是容易发生瓶颈的地方。也就是说,您需要了解您的应用程序(配置文件)。集中优化时间的最佳位置几乎总是在查询本身的数据库级别,而不是客户端代码。客户端代码只是管道:它不是您的应用程序的主要力量。然而,由于管道倾向于在许多不同的应用程序中重复使用,我确实很同情尽可能接近最佳状态的愿望,因此对于如何构建该代码,我确实有很多话要说。

    我的数据层中有一个用于选择查询/过程的通用方法,如下所示:

    private static IEnumerable<IDataRecord> Retrieve(string sql, Action<SqlParameterCollection> addParameters)
    {
        //ConnectionString is a private static property in the data layer
        // You can implement it to read from a config file or elsewhere
        using (var cn = new SqlConnection(ConnectionString))
        using (var cmd = new SqlCommand(sql, cn))
        {
            addParameters(cmd.Parameters);
    
            cn.Open();
            using (var rdr = cmd.ExecuteReader())
            {
                while (rdr.Read())
                    yield return rdr;
                rdr.Close();
            }
        }
    }
    

    这让我可以编写使用匿名方法添加参数的公共数据层方法。显示的代码适用于 .Net 2.0+,但使用 .Net 3.5 可以写得更短:

    public IEnumerable<IDataRecord> GetFooChildrenByParentID(int ParentID)
    {
        //I could easily use a stored procedure name instead of a full sql query
        return Retrieve(
            @"SELECT c.* 
             FROM [ParentTable] p 
             INNER JOIN [ChildTable] c ON c.ParentID = f.ID 
             WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
           {
              p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
           }
         );
    }
    

    我将在此停止,以便再次向您指出上面使用匿名方法创建参数的代码。

    这是非常干净的代码,因为它将查询定义和参数创建放在同一个地方,同时仍然允许您将样板数据库连接/调用代码抽象到更可重用的地方。我认为您问题中的任何要点都没有涵盖这种技术,而且它的速度也非常快。我认为这涵盖了您问题的主旨。


    不过,我想继续解释这一切是如何结合在一起的。其余部分相当简单,但也很容易将其扔到列表或类似列表中并出错,最终损害性能。继续前进,业务层然后使用工厂将查询结果转换为对象(c# 3.0 或更高版本):

    public class Foo
    {
        //various normal properties and methods go here
    
        public static Foo FooFactory(IDataRecord record)
        {
            return new Foo
            {
                Property1 = record[0],
                Property2 = record[1]
                //...
            };
        }
    }
    

    除了将它们放在它们的类中之外,您还可以将它们全部组合到一个静态类中,专门用于保存工厂方法。

    我需要对原始检索方法进行一项更改。该方法一遍又一遍地“产生”相同的对象,但这并不总是那么好。为了让它工作,我们想要做不同的事情是强制复制由当前记录表示的对象,这样当阅读器为下一条记录发生变异时,我们正在使用干净的数据。我等到展示了工厂方法之后,才能在最终代码中使用它。新的 Retrieve 方法如下所示:

    private static IEnumerable<T> Retrieve(Func<IDataRecord, T> factory,
                      string sql, Action<SqlParameterCollection> addParameters)
    {
        //ConnectionString is a private static property in the data layer
        // You can implement it to read from a config file or elsewhere
        using (var cn = new SqlConnection(ConnectionString))
        using (var cmd = new SqlCommand(sql, cn))
        {
            addParameters(cmd.Parameters);
    
            cn.Open();
            using (var rdr = cmd.ExecuteReader())
            {
                while (rdr.Read())
                    yield return factory(rdr);
                rdr.Close();
            }
        }
    }
    

    现在我们可以像这样调用这个新的 Retrieve() 方法:

    public IEnumerable<Foo> GetFooChildrenByParentID(int ParentID)
    {
        //I could easily use a stored procedure name instead of a full sql query
        return Retrieve(Foo.FooFactory,
            @"SELECT c.* 
             FROM [ParentTable] p 
             INNER JOIN [ChildTable] c ON c.ParentID = f.ID 
             WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
           {
              p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
           }
         );
    }
    

    显然,最后一种方法可以扩展为包含所需的任何其他业务逻辑。事实证明,这段代码非常快,因为它利用了 IEnumerable 的惰性求值特性。缺点是它往往会创建许多短期对象,这可能会损害您询问的事务性能。为了解决这个问题,我有时会打破良好的 n 层并将 IDataRecord 对象直接传递到表示层,并避免为立即绑定到网格控件的记录创建不必要的对象。

    更新/创建代码类似,不同之处在于您通常一次只更改一条记录,而不是多条记录。

    或者,我可以省去你阅读这篇长篇文章,告诉你使用实体框架;)

    【讨论】:

    • 不错的技巧,Action 回调参数化查询和匿名委托闭包。
    • 把最后一行放在最上面。 :-)
    • “或者,我可以省去你阅读这篇长文的麻烦,只告诉你使用实体框架;)” - OP 说他想要 方法。除了最琐碎的任务之外,EF 的速度还远远不够
    • @Lijo - 不,这不是性能问题。情况更糟。在错误的情况下,除非您将数据记录值复制到此级别的新对象,否则查询中的每条记录都将显示最后一条记录中的数据。
    • @RoyiNamir IDataRecord 是提供从列中获取值所需的功能的接口。 IDataReader juts 实现了 IDataRecord,然后添加了一些用于移动到下一行或结果集的功能。在此方案中,您不希望有人从您下方更改当前行,因此 IDataRecord 更合适。此外,希望有一天微软最终也让 DataRow 类型(与数据表一起使用)实现该接口,这将使该代码与也使用数据集/数据表的代码兼容。
    【解决方案2】:

    就我个人而言,我是代码生成的忠实粉丝。我推出了自己的自制 XML,并在构建时通过 XSLT 运行它以生成我的 .CS 文件。我在这篇文章Using XSLT to generate Performance Counters code 中描述了这个过程。尽管该链接讨论了生成性能计数器代码,但我使用相同的过程来生成我的 DAL。

    所以我会创建一个类似的 XML:

    <query name="LoadCustomerByName" returns="Customer">
      <parameter name="name" type="String"/>
      <text>SELECT ... FROM Customers WHERE name=@name</text>
    </query>
    

    然后 XLST 会将其转换为:

    public Customer LoadCustomerByName(
      SqlConnection conn,
      SqlTransaction trn,
      String name)
    {
      using (Sqlcommand cmd = new SqlCommand(@"SELECT ... FROM ...", conn, trn))
      {
        cmd.Parameters.AddWithValue("@name", name);
        using (SqlDataReader rdr = cmd.ExecuteReader ())
        {
          Customer c = new Customer();
          // Load c from rdr
          return c;
        }
      }
    }
    

    现在我详细介绍了 XSLT 转换的实际作用,但真正重要的是,这种方法让我可以完全控制如何创建 DAL,并且它在各个方面都很灵活,因为生成的 . CS 代码完全由我的 XSLT 驱动。我可以更改 XLST,这将导致在我的 DAL 中重新生成每个方法。它使探索各种解决方案变得容易,它允许我在代码中添加检测(例如测量每个单独查询性能和使用频率的计数器)等等。

    这基本上是各种 VS 设计人员为您做的事情,但是如果您采取额外的步骤来控制代码生成过程,您将拥有更多的灵活性和对结果的控制。

    【讨论】:

      【解决方案3】:

      执行时间最快还是编程时间最快?要增加 #1 的吞吐量,您唯一可以做的就是使用多个线程和连接来进行插入 - 您可以使用 SQLCommand.BeginExecuteNonQuery 来做到这一点

      【讨论】:

        【解决方案4】:

        这个已经有一段时间了,但如果你愿意使用非常微的 ORM,只需使用dapper-dot-net

        它的语法非常简洁,速度极快,并且易于放入任何项目中。它正在 StackOverflow 的生产环境中使用,这个页面很可能是由它带给你的。支持所有常用的 SQL 方法作为扩展方法,并支持一切异步。

        一些性能比较:

        【讨论】:

          【解决方案5】:

          这与插入或更新无关,但我使用各种方法对读取速度进行了一些基准测试。 Of all, DataTable routes seemed slower of the bunch.。 Joel 的方法基本上是你能得到的最快的..

          【讨论】:

            【解决方案6】:

            我不喜欢的一件事是 yield return 不能驻留在 try...catch 块中。因此,无法容纳集中式异常处理/日志记录。

            我使用了类似的方法,但传递了一个 IEnumerable 作为参数。那我就不用yield return了。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2014-10-26
              • 2014-10-05
              • 1970-01-01
              • 2014-09-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多