比使用 LINQ 更快地查询数据库的每条记录

Posted

技术标签:

【中文标题】比使用 LINQ 更快地查询数据库的每条记录【英文标题】:Querying database for every record faster than using LINQ 【发布时间】:2016-10-19 10:38:15 【问题描述】:

我有大约 700K 行正在迭代。对于每一行,在数据库上运行一个 SELECT sql 语句来检查当前记录中的“名称”字段是否存在于相应的表中。

数据库读取 700K 次让我觉得效率非常低,所以我选择在循环之前读取所有数据,将其存储在 DataTable 中,并通过 LINQ 检查相应记录是否包含在每个 DataTable 中迭代。

这样做时,性能会下降很多。该过程现在需要大约两倍的时间才能完成(通过基准测试多次证明)。

这是原始(更快)代码:

for (int index = 0; index < dtSightings.Rows.Count; index++)

   DataTable dtResults = Utilities.ExecuteQuerymysqlString(connectionString, "SELECT name FROM my_table WHERE name = @name AND month_year = @monthYear", dictionary);

   if (dtResults == null || dtResults.Rows.Count == 0)
   
   //Continue
   


public static DataTable ExecuteQueryMysqlString(string connectionString, string sql, Dictionary<string, object> listParameters)
        
            DataTable dtResults = new DataTable();

            if (string.IsNullOrWhiteSpace(connectionString) == false)
            
                connectionString += ";Allow User Variables=True;";

                try
                
                    using (MySqlConnection connection = new MySqlConnection(connectionString))
                    
                        connection.Open();

                        using (MySqlCommand cmd = connection.CreateCommand())
                        
                            cmd.CommandTimeout = 0;
                            cmd.CommandText = sql;

                            if (listParameters != null && listParameters.Count > 0)
                            
                                foreach (string currentKey in listParameters.Keys)
                                
                                    cmd.Parameters.Add(new MySqlParameter(currentKey, GetDictionaryValue(listParameters, currentKey)));
                                
                            

                            using (MySqlDataAdapter da = new MySqlDataAdapter(cmd))
                            
                                da.Fill(dtResults);
                            
                        
                    

                    return dtResults;
                
                catch (Exception ex)
                
                    MessageBox.Show("ERROR: " + ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return dtResults;
                
            
            else
            
                return dtResults;
            
        

这是“优化”(但速度较慢)的代码:

DataTable dt= Utilities.ExecuteQueryMysqlString(connectionString, "SELECT name, month_year FROM my_table", null);

for (int index = 0; index < dtSightings.Rows.Count; index++)

  DataRow row = dt.AsEnumerable().Where(r => r.Field<string>("name").Equals(name, StringComparison.InvariantCultureIgnoreCase) && r.Field<DateTime>("month_year") == new DateTime(billYear, billMonth, 1)).FirstOrDefault();

  if (hasResidentBeenDiscoveredPreviously == null)
  
    //Continue
  

我不明白为什么第一种方法要快得多。是否有比第二种方法更优化的方法?

【问题讨论】:

看起来你正在交叉引用同一个数据库中的两个表,为什么不让 SQL 处理这项工作并让它返回一个不在这两个表中的记录列表。 Left joinwhere not exists 会创造奇迹。 其实只有一张表——“my_table” 如果需要,您可以将表连接到自身。 一些改进:使用AsQueryable 代替AsEnumerable,在Linq 外部创建一个变量,在这里您只声明一次新的DateTime(billYear, billMonth, 1)),然后使用Linq 内部的处理程序 请让我们了解更多关于 dtSightings 的内容。 【参考方案1】:

LINQ 方法很慢,因为Where 基本上是线性搜索,在循环中执行时,确实会减慢处理速度。

您真正需要的是基于哈希的快速查找数据结构。我建议您使用带有这样的自定义数据的HashSet(主要是为了支持不区分大小写的名称查找):

public struct NameDatePair : IEquatable<NameDatePair>

    public readonly string Name;
    public readonly DateTime Date;
    public NameDatePair(string name, DateTime date)  Name = name; Date = date; 
    static IEqualityComparer<string> NameComparer   get  return StringComparer.InvariantCultureIgnoreCase;  
    public override int GetHashCode()  return NameComparer.GetHashCode(Name) ^ Date.GetHashCode(); 
    public override bool Equals(object obj)  return obj is NameDatePair && Equals((NameDatePair)obj); 
    public bool Equals(NameDatePair other)  return NameComparer.Equals(Name, other.Name) && Date == other.Date; 

以下是在您的情况下如何使用它(它应该比您的两种方法都快得多):

var dt = Utilities.ExecuteQueryMysqlString(connectionString, "SELECT name, month_year FROM my_table", null);
var nameDatePairSet = new HashSet<NameDatePair>(dt.AsEnumerable().Select(
    r => new NameDatePair(r.Field<string>("name"), r.Field<DateTime>("month_year"))));

for (int index = 0; index < dtSightings.Rows.Count; index++)

    var dr = dtSightings.Rows[index];
    var name = dr.Field<string>("name");
    var billYear = dr.Field<int>("billYear");
    var billMonth = dr.Field<int>("billMonth");
    bool exists = nameDatePairSet.Contains(new NameDatePair(name, new DateTime(billYear, billMonth, 1)));

(由于你没有显示变量namebillYearbillMonth来自哪里,上面的代码有一些猜测,你可以根据需要调整)

【讨论】:

如果可行的话,SQL 自连接会快很多。【参考方案2】:

您的两个代码示例都具有这种基本结构。

1. For each row in one bunch of rows ...
  2. read the rows in another bunch of rows ...
    3. to identify a particular situation.

这就是 O(n-squared) 算法的定义。它的性能很差,并且随着您获得更多数据而变得更糟。

您的第一个示例更快,因为您使用SELECT 来读取第 2 步中的行,并且您的 DMBS 可能会对其进行优化。在您的第二个示例中,在第 2 步中,您针对第 1 步中的每一行遍历所有行。

诀窍是把事情搞好,这样你只需要通过第 2 步的表格一次。

很难准确地说出你在用dtSightings 做什么:你运行了索引变量,但你似乎没有在任何地方使用它。无论如何,这个算法大纲应该让你从 O(n-squared)O(n log n)

1. make a HashSet ready to hold results for matching in dtSightings.
2. for each row in dtSightings ...
   a. populate that row into the HashSet
3. for each row in your query..
   b. Look it up in your HashSet
   c. If you get a hit (a non-match, I believe) report it.

第 2 步和第 3 步各花费 O(n) 时间:它们与您正在处理的行数成正比。每次运行时,子步骤 b 都需要 O(log n)。这就是 O(n log n) 的来源。

任何处理大量数据的程序员都需要考虑计算复杂性——O(n) 的东西——才能成功。

【讨论】:

感谢您的解释! +1

以上是关于比使用 LINQ 更快地查询数据库的每条记录的主要内容,如果未能解决你的问题,请参考以下文章

使用记录集中的字段作为每个查询的参数,为记录集中的每条记录运行和追加查询

使用 Linq C# 为 DataTable 的每条记录创建 XML 文件

根据查询中的每条记录将报告打印为 PDF

如何优化限制查询以便从庞大的表中更快地访问数据?

如何优化限制查询以更快地从大表中访问数据?

Linq MVC5 MSQL 从包含大量列的表中选择一些列,以便更好更快地查询