【问题标题】:Linq To Sql surprisingly fast retreving data. Is it normal that it is 10x faster than ADO?Linq To Sql 检索数据的速度惊人。它比 ADO 快 10 倍是否正常?
【发布时间】:2012-02-18 13:47:10
【问题描述】:

我目前正在学习 Linq to Sql,我对选择数据的性能感到非常惊讶。我正在从几个表中检索连接数据。我选择了大约 40k 行。使用 ADO 将这些数据映射到对象大约需要 35 秒,使用 NHbiernate 大约需要 130 秒,而使用 Linq To Sql 的可疑之处只有 3.5 秒。此外,我想写下我正在使用立即加载,如下所示:

THESIS th = new THESIS(connectionString);
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<NumericFormula>(x => x.RPN);
dlo.LoadWith<RPN>(x => x.RPNDetails);
dlo.LoadWith<RPNDetail>(x => x.Parameter);
th.LoadOptions = dlo;
th.Log = Console.Out;

在我迭代时查看日志,我看不到 Linq To Sql 生成一些额外的数据库查询。

我对性能上的巨大差异感到非常惊讶,我想知道我可能不明白一些事情。

有人能解释一下为什么它运行得这么快吗? 为了测量时间,我正在使用 Stopwatch 类。

ADO.NET 代码:

public static List<NumericFormulaDO> SelectAllNumericFormulas()
{
    var nFormulas = new List<NumericFormulaDO>();

    string queryString = @"
        SELECT *
        FROM    NumericFormula nf 
                Left Join Unit u on u.Unit_Id = nf.Unit_Id 
                Left Join UnitType ut on ut.UnitType_Id = u.UnitType_Id 
                Join RPN r on r.RPN_Id = nf.RPN_Id 
                Join RPNDetails rd on rd.RPN_Id = r.RPN_Id 
                Join Parameter par on par.Parameter_Id = rd.Parameter_Id where nf.NumericFormula_Id<=10000";

    using (var connection = new SqlConnection(connectionString))
    {
        var command = new SqlCommand(queryString, connection);
        connection.Open();
        using (var reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                var det = new RPNDetailsDO();
                det.RPNDetails_Id = Int32.Parse(reader["RPNDetails_Id"].ToString());
                det.RPN_Id = Int32.Parse(reader["RPN_Id"].ToString());
                det.Identifier = reader["Identifier"].ToString();
                det.Parameter.Architecture = reader["Architecture"].ToString();
                det.Parameter.Code = reader["Code"].ToString();
                det.Parameter.Description = reader["Description"].ToString();
                det.Parameter.Parameter_Id = Int32.Parse(reader["Parameter_Id"].ToString());
                det.Parameter.ParameterType = reader["ParameterType"].ToString();
                det.Parameter.QualityDeviationLevel = reader["QualityDeviationLevel"].ToString();

                if (nFormulas.Count > 0)
                {
                     if (nFormulas.Any(x => x.RPN.RPN_Id == Int32.Parse(reader["RPN_Id"].ToString())))
                     {
                         nFormulas.First(x=>x.RPN.RPN_Id == Int32.Parse(reader["RPN_Id"].ToString())).RPN.RPNDetails.Add(det);
                     }
                     else
                     {
                         NumericFormulaDO nFormula = CreatingNumericFormulaDO(reader, det);
                         nFormulas.Add(nFormula);
                         //System.Diagnostics.Trace.WriteLine(nFormulas.Count.ToString());
                     }
                }
                else
                {
                     NumericFormulaDO nFormula = CreatingNumericFormulaDO(reader, det);
                     nFormulas.Add(nFormula);
                     //System.Diagnostics.Trace.WriteLine(nFormulas.Count.ToString());
                }
            }
        }
    }

    return nFormulas;
}

private static NumericFormulaDO CreatingNumericFormulaDO(SqlDataReader reader, RPNDetailsDO det)
{
    var nFormula = new NumericFormulaDO();
    nFormula.CalculateDuringLoad = Boolean.Parse(reader["CalculateDuringLoad"].ToString());
    nFormula.NumericFormula_Id = Int32.Parse(reader["NumericFormula_Id"].ToString());
    nFormula.RPN.RPN_Id = Int32.Parse(reader["RPN_Id"].ToString());
    nFormula.RPN.Formula = reader["Formula"].ToString();
    nFormula.Unit.Name = reader["Name"].ToString();

    if (reader["Unit_Id"] != DBNull.Value)
    {
        nFormula.Unit.Unit_Id = Int32.Parse(reader["Unit_Id"].ToString());
        nFormula.Unit.UnitType.Type = reader["Type"].ToString();
        nFormula.Unit.UnitType.UnitType_Id = Int32.Parse(reader["UnitType_Id"].ToString());
    }
    nFormula.RPN.RPNDetails.Add(det);
    return nFormula;
}

LINQ to SQL 代码:

THESIS th = new THESIS(connectionString);
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<NumericFormula>(x => x.RPN);
dlo.LoadWith<RPN>(x => x.RPNDetails);
dlo.LoadWith<RPNDetail>(x => x.Parameter);
th.LoadOptions = dlo;
th.Log = Console.Out;
var nFormulas = 
    th.NumericFormulas.ToList<NumericFormula>();

NHibernate 代码:

IQueryable<NumericFormulaDO> nFormulas =
    session.Query<NumericFormulaDO>()
        .Where(x=>x.NumericFormula_Id <=10000);

List<NumericFormulaDO> nForList =
    new List<NumericFormulaDO>();

nForList = nFormulas.ToList<NumericFormulaDO>();

与您的 cmets 相关,您可以看到在 ADO 中我使用的是 SqlReader,而在 LINQ 中我尝试使用立即执行。

当然,我在 ADO 部分中的映射“算法”可能不是很好,但 NHibernate 比 ADO 慢得多(慢 4 倍)所以我想知道在 LINQ to SQL 部分中是否一切正常,因为我认为在 NHibernate 中一切都很好,毕竟它比令人困惑的 ADO 部分慢得多。

谢谢大家的回复。

【问题讨论】:

  • 您能否发布您的 ADO 代码,以便我们查看您是否做错了什么?
  • 您实际上是使用 LINQ-to-SQL 检索数据,还是只是构建了一个查询,该查询在执行时会检索数据(延迟执行)?
  • 如果您可以发布所有查询代码,比较 ADO.NET、LINQ to SQL 和 NHibernate 会很有帮助。
  • 您在使用 ADO.NET 执行阅读器和使用 linqtosql 时不会做同样的事情。使用阅读器时,您拥有整个 if(nFormulas.Count &gt;0) 部分...可能需要一些时间...尝试修改您的 queryString 以达到相同的效果。
  • @PiotrCzarnecki - 您的 ADO 代码效率极低。听从MSDN Documentation 的建议:因为基于序号的查找比命名查找更有效,所以在循环中调用 GetOrdinal 是低效的。通过调用一次 GetOrdinal 并将结果分配给一个整数变量以在循环中使用来节省时间。

标签: c# database linq nhibernate linq-to-sql


【解决方案1】:

LINQ-to-SQL 消耗 ADO.NET 并且有额外的开销,所以不:它不应该更快,除非它不做同样的工作。有人提到通过序数与名称进行访问,但坦率地说,这会影响微秒,而不是秒。它无法解释数量级的变化。

回答这个问题的唯一方法是跟踪 LINQ-to-SQL 正在做什么。幸运的是,这很简单——你可以这样做:

dbContext.Log = Console.Out;

将写入 TSQL 的内容执行到控制台。那么有两种选择:

  1. 您发现 TSQL 没有做同样的事情(可能不是急切加载)
  2. 您发现 TSQL 是有效的(=做同样的事情),但有更好的计划 - 在这种情况下...“借用”它:p

一旦您有了要比较的 TSQL,就可以并排测试 那个,这样您就可以测试相同的工作了。如果您想在没有开销的情况下获得便利,我会考虑“dapper” - 消除了将读取器映射到对象的无聊繁重工作,但非常优化。

【讨论】:

  • 感谢您的回复我借用了 linq 生成的查询,是的,您说得对,这个查询做的事情并不完全相同。但是当我执行那个查询和我的旧 ADO 查询时,这个由 linq 生成的时间更长。
  • 我理解这种情况,执行查询在 ADO 中可能更快,但整个映射(使用读取器迭代和分配值)非常耗时。对于简单的选择(和映射“算法”),ADO 更快(无需加入等)。我仍然想知道为什么 Linq To SQL 映射得如此之快。就像我之前说的,我知道我的 ADO 代码可能不太好,但是这个示例中的 Linq 比 NHibernate 快得多。还有其他想法吗?
  • @Piotr 哦,我希望它确实快速映射。实际上,它没有 - 这正是我们(Stack Exchange)写“dapper”的原因。相比之下,LINQ-to-SQL 的实现速度非常慢。是的,NHibernate 在这方面也很慢(相对)(这种灵活/抽象模型的“税”)。
  • 大家好,我分析了 ADO 应用程序,分析器显示 87% 的时间花在 .Any() 方法上,10% 时间花在 .First() 方法上。所以我认为这就是为什么需要这么长时间的原因。但是在复杂的查询结果中,在 Reader 上迭代期间很难省略一些附加条件、内部循环或内部 linq 到对象表达式。我尝试了带有附加嵌套连接和两个查询字符串的选项,以避免 linq to objects 方法。但是 while(reader2.Read()) 嵌套在其他 while(reader.Read()) 循环中的方法时间更长。
  • 不要同时使用 Any 和 First,而是考虑 FirstOrDefault() 然后检查结果是否为 != null,我敢打赌它可以为您节省大量时间。此外,Parse(ToString) 效率非常。 (请参阅:fxcopcontrib.codeplex.com/…)而不是每次都遍历 NumericFormula 列表来执行 Any 和 First,考虑使用 Dictionary 以便您可以使用 id 等有效地查找现有项目...这些是 Linq-2-sql 高效的地方。
【解决方案2】:

根据以上说明重写了ADO.NET代码,这样应该会快很多。您仍然可以通过使用序数值而不是列名以及按照与查询中完全相同的顺序读取字段来进行改进,但这些都是微优化。

我还删除了几个重复项。 You might also want to check how to improve the performance of typecasting and conversions,因为 Parse(ToString) 路由效率非常低,并且在运行以不同语言运行的系统时会导致非常奇怪的问题。当涉及小数、浮点数或双精度时,在进行这些转换时也有可能丢失数据,因为并非所有值都可以正确转换为字符串(或无法往返)。

public static List<NumericFormulaDO> SelectAllNumericFormulas()
{
    var nFormulas = new Dictionary<int, NumericFormulaDO>();

    string queryString = @"
    SELECT *
    FROM    NumericFormula nf 
            Left Join Unit u on u.Unit_Id = nf.Unit_Id 
            Left Join UnitType ut on ut.UnitType_Id = u.UnitType_Id 
            Join RPN r on r.RPN_Id = nf.RPN_Id 
            Join RPNDetails rd on rd.RPN_Id = r.RPN_Id 
            Join Parameter par on par.Parameter_Id = rd.Parameter_Id where nf.NumericFormula_Id<=10000";

    using (var connection = new SqlConnection(connectionString))
    {  
        connection.Open();
        using (var command = new SqlCommand(queryString, connection));
        using (var reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                var det = new RPNDetailsDO();
                det.RPNDetails_Id = (int) reader.GetValue("RPNDetails_Id");
                det.RPN_Id = (int) reader.GetValue("RPN_Id");
                det.Identifier = (string) reader.GetValue("Identifier");
                det.Parameter.Architecture = (string)reader.GetValue("Architecture");
                det.Parameter.Code = (string)reader.GetValue("Code");
                det.Parameter.Description = (string)reader.GetValue("Description");
                det.Parameter.Parameter_Id = (int) reader.GetValue("Parameter_Id");
                det.Parameter.ParameterType = (string)reader.GetValue("ParameterType");
                det.Parameter.QualityDeviationLevel = (string)reader.GetValue("QualityDeviationLevel");

                NumericFormulaDO parent = null;
                if (!nFormulas.TryGetValue((int)reader.GetValue("RPN_Id"), out parent)
                {
                    parent = CreatingNumericFormulaDO(reader, det);
                    nFormulas.Add(parent.RPN.RPNID, parent);
                }
                else
                {
                    parent.RPN.RPNDetails.Add(det);
                }
            }
        }
    }

    return nFormulas.Values.ToList();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-01
    • 1970-01-01
    • 2012-07-08
    • 2011-07-19
    相关资源
    最近更新 更多