【问题标题】:What is the fastest way to read data from a DbDataReader?从 DbDataReader 读取数据的最快方法是什么?
【发布时间】:2011-08-11 03:21:30
【问题描述】:

在下面的代码中,command 是一个已经设置好的 DbCommand:

using( var dataReader = command.ExecuteReader() /*The actual execution of the query takes relatively little time.*/ ) {
                while( dataReader.Read() ) {
                    // These are what take all of the time. Replacing them all with reader.GetValues( myArray ) has no impact.
                    val0 = dataReader.GetValue( 0 );
                    val1 = dataReader.GetValue( 1 );
                    val2 = dataReader.GetValue( 2 );
                }
            }

我目前正在处理的查询的大部分时间都花在了 GetValue 调用上。是否为每个 GetValue 调用往返于数据库?似乎是这样,这似乎非常低效。正如代码所指出的,尝试使用 GetValues() 一次性完成并没有什么不同。有没有办法一次性获得整行?更好的是,有没有办法一次性获得整个结果集?

谢谢。

【问题讨论】:

  • 不写你的实现,IDataReader 和/或 DbDataReader 和 "GetStrongScalarType(ordinalNumber) 是更快的。GetString、GetInt32 等和 0、1、2 或序数。在数据的末尾,填充 DataTable 或 DataSet 或大多数 ORM 使用 IDataReader 和/或 DbDataReader,并使用序数。ORM 可以使用 ("byStringColumnName"),但它们通常(一次)将它们映射到序数并缓存它以供重复调用。示例ORM : github.com/nhibernate/nhibernate-core/blob/master/src/…
  • 不要使用“GetValue”。使用具体的 GetString、GetInt32 等以获得最佳性能。

标签: c# .net database


【解决方案1】:

我用各种方法对自己进行了一些基准测试:

public DataTable Read_using_DataTable_Load(string query)
{
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = query;
        cmd.Connection.Open();
        var table = new DataTable();
        using (var r = cmd.ExecuteReader())
            table.Load(r);
        return table;
    }
}

public DataTable Read_using_DataSet_Fill<S>(string query) where S : IDbDataAdapter, IDisposable, new()
{
    using (var da = new S())
    {
        using (da.SelectCommand = conn.CreateCommand())
        {
            da.SelectCommand.CommandText = query;
            DataSet ds = new DataSet();
            da.Fill(ds);
            return ds.Tables[0];
        }
    }
}

public IEnumerable<S> Read_using_yield_selector<S>(string query, Func<IDataRecord, S> selector)
{
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = query;
        cmd.Connection.Open();
        using (var r = cmd.ExecuteReader())
            while (r.Read())
                yield return selector(r);
    }
}

public S[] Read_using_selector_ToArray<S>(string query, Func<IDataRecord, S> selector)
{
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = query;
        cmd.Connection.Open();
        using (var r = cmd.ExecuteReader())
            return ((DbDataReader)r).Cast<IDataRecord>().Select(selector).ToArray();
    }
}

public List<S> Read_using_selector_into_list<S>(string query, Func<IDataRecord, S> selector)
{
    using (var cmd = conn.CreateCommand())
    {
        cmd.CommandText = query;
        cmd.Connection.Open(); 
        using (var r = cmd.ExecuteReader())
        {
            var items = new List<S>();
            while (r.Read())
                items.Add(selector(r));
            return items;
        }
    }
}

1 和 2 返回 DataTable 而其余的强类型结果集,所以它完全不是苹果对苹果,但我会相应地计时。

只是必需品:

Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
    Read_using_DataTable_Load(query); // ~8900 - 9200ms

    Read_using_DataTable_Load(query).Rows.Cast<DataRow>().Select(selector).ToArray(); // ~9000 - 9400ms

    Read_using_DataSet_Fill<MySqlDataAdapter>(query); // ~1750 - 2000ms

    Read_using_DataSet_Fill<MySqlDataAdapter>(query).Rows.Cast<DataRow>().Select(selector).ToArray(); // ~1850 - 2000ms

    Read_using_yield_selector(query, selector).ToArray(); // ~1550 - 1750ms

    Read_using_selector_ToArray(query, selector); // ~1550 - 1700ms

    Read_using_selector_into_list(query, selector); // ~1550 - 1650ms
}

sw.Stop();
MessageBox.Show(sw.Elapsed.TotalMilliseconds.ToString());

查询返回大约 1200 行和 5 个字段(运行 100 次)。除了Read_using_Table_Load之外都表现不错。

我更喜欢Read_using_yield_selector,它会延迟返回数据,如枚举。如果您只需要枚举它,这对记忆很有帮助。要在内存中保存集合的副本,最好使用Read_using_selector_ToArrayRead_using_selector_into_list

【讨论】:

  • +1 - 有趣,尤其是DataTable.Load()IDataAdapter.Fill() 之间的比较。我原以为数据适配器与直接加载到DataTable 相同或更慢,但看起来它快了 4-5 倍?知道为什么吗?
  • @TimMedora 不知道,但我确认它也尝试使用SQLite Connector 产生了与我回答here 相同的结果。尽管如此,它还是很奇怪。
  • @TimMedora:您需要用.BeginLoadData().EndLoadData() 包围DataTable.Load(),以达到与DataSet 相同的速度。
  • 这个答案包含有用的信息,但没有解决 OP 的问题。
  • @WinodPatel 你会做类似的事情:var users = Read5("select * from User", r =&gt; new User { Id = r[0], Name = r[1] })。希望清楚。
【解决方案2】:
using (connection)
    {
        SqlCommand command = new SqlCommand(
          "SELECT CategoryID, CategoryName FROM dbo.Categories;" +
          "SELECT EmployeeID, LastName FROM dbo.Employees",
          connection);
        connection.Open();

        SqlDataReader reader = command.ExecuteReader();

        while (reader.HasRows)
        {
            Console.WriteLine("\t{0}\t{1}", reader.GetName(0),
                reader.GetName(1));

            while (reader.Read())
            {
                Console.WriteLine("\t{0}\t{1}", reader.GetInt32(0),
                    reader.GetString(1));
            }
            reader.NextResult();
        }
    }

【讨论】:

  • 这并不比我在原始问题中发布的代码快(大致相同),但我认为这是最快的答案。至少我们现在知道,没有比我想的更快的方法了。
【解决方案3】:

我会使用dapper-dot-net 之类的东西将其加载到基本类型模型中;这是一个微 ORM,因此您可以获得元编程(有效地预生成 IL 等)的好处 - 没有 EF 或 DataTable 之类的开销。

【讨论】:

  • 我认为这对他关于数据阅读器的具体问题没有帮助。 Dapper 本身在后台使用 DbDataReader.GetValue。
【解决方案4】:
        Dim adapter As New Data.SqlClient.SqlDataAdapter(sqlCommand)
        Dim DT As New DataTable
        adapter.Fill(DT)

【讨论】:

    【解决方案5】:

    您可以使用DbDataAdapter 获取所有结果并将它们存储在DataTable 中。

    【讨论】:

      【解决方案6】:

      使用无类型数据集。据我所知,这是最快的。

      【讨论】:

      • 为什么你认为它是“最快的”?您没有提供此答案背后的任何理由。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-26
      • 2020-10-06
      • 1970-01-01
      • 2019-09-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多