5

我有包含数十亿行的数据库。我创建了从用户数量的参数接收并通过这些参数切割数据库的函数。这对我来说很适合小 DB(30000 行),但是当我尝试在大 DB 上使用这个函数时,我得到了TIMEOUTEXCEPTION from SQLSERVER.

这是我的代码:

public static IQueryable<LogViewer.EF.InternetEF.Log> ExecuteInternetGetLogsQuery(FilterCriteria p_Criteria, ref GridView p_Datagrid)
{
    IQueryable<LogViewer.EF.InternetEF.Log> internetQuery = null;

    using (InternetDBConnectionString context = new InternetDBConnectionString())
    {
        internetQuery = context.Logs;
        if ((p_Criteria.DateTo != null && p_Criteria.DateFrom != null))
        {
            internetQuery = internetQuery.Where(c => c.Timestamp >= p_Criteria.DateFrom && c.Timestamp < p_Criteria.DateTo);
        }
        else if (p_Criteria.DateFrom != null && p_Criteria.DateFrom > DateTime.MinValue)
        {
            internetQuery = internetQuery.Where(c => c.Timestamp >= p_Criteria.DateFrom);
        }
        else if (p_Criteria.DateTo != null && p_Criteria.DateTo > DateTime.MinValue)
        {
            internetQuery = internetQuery.Where(c => c.Timestamp < p_Criteria.DateTo);
        }
        if (!string.IsNullOrEmpty(p_Criteria.FreeText))
        {
            internetQuery = internetQuery.Where(c => c.FormattedMessage.Contains(p_Criteria.FreeText));
        }

        if (p_Criteria.Titles.Count > 0)
        {
            internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.Titles.Contains(c.Title)).AsQueryable();
        }
        if (p_Criteria.MachineNames.Count > 0)
        {
            internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.MachineNames.Contains(c.MachineName)).AsQueryable();
        }
        if (p_Criteria.Severities.Count > 0)
        {
            internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.Severities.Contains(c.Severity)).AsQueryable();
        }
        internetQuery= internetQuery.OrderByDescending(c=>c.LogID);
        if (internetQuery.Count() > p_Criteria.TopValue)
        {
            internetQuery = internetQuery.Take(p_Criteria.TopValue);
        }
        p_Datagrid.DataSource = internetQuery;
        p_Datagrid.DataBind();
        return internetQuery;

    }  
}

我的 SQL 版本是 2005。我p_Datagrid.DataBind();在行上遇到了异常。

有什么建议吗?谢谢

4

6 回答 6

3

我可以看到您有以下选择:

  • 增加超时时间(Bad idé只是在将来移动问题)
  • 而不是进行 linq 查询。通过存储过程获取数据
  • 制作网格页面。因此,您只需检索目标页面的数据。
  • 查看查询计划,看看您是否可以在您正在执行where语句的列上执行任何索引 和order by
  • 为什么需要在数据网格中有数十亿行。有什么要求?也许你可以只显示top 1000top 10000。因为从用户那里我看不到任何看到十亿行网格的优点。

那只是我的想法。

编辑

如果我有这个功能,我会开始查看这部分代码:

if (p_Criteria.Titles.Count > 0)
{
     internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.Titles.Contains(c.Title)).AsQueryable();
}
if (p_Criteria.MachineNames.Count > 0)
{
      internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.MachineNames.Contains(c.MachineName)).AsQueryable();
}
if (p_Criteria.Severities.Count > 0)
{
      internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.Severities.Contains(c.Severity)).AsQueryable();
}

这实际上使结果成为 IEnumerable,然后您where使用数据库调用执行内存中的语句。您可能还会遇到问题,因为当您调用相关表时,它会调用数据库。也许您可以获取行,然后contains使用IQueryable. 这样做时有IQueryable尿布的所有优点。

于 2012-04-17T12:13:43.343 回答
2

一般来说,像这样的“瑞士军刀”规范或标准模式很难优化(即 SQL 级别的索引),因为客户端/用户可以指定大量的过滤器组合排列。因此,如果您可以以某种方式强制用户指定至少一个强制性标准,从而显着减少行数,例如通过强制日期范围且不超过一个月,我会从那里开始,因为至少我们有一些东西当我们查看索引时开始。

由于可能存在大量行,我会断言或验证p_Criteria.TopValue用于限制行的值始终存在,并且是一个合理的数字,例如Take(1000). 如果达到此阈值,您始终可以警告用户缩小他/她的搜索范围。

主要问题可能是过滤Titles,MachineNamesSeveritieseach 调用AsEnumerable(),这使查询具体化,因此您在内存中评估这 3 个过滤器,而不是在 SQL 中,可能有大量记录。所有最新版本的 EF 都能够将形式的谓词转换Where(c => IEnumerable<X>.Contains(c.Column))为 SQL WHERE c.Column IN (X1, X2, X3)

即您应该删除AsEnumerable()这 3 个过滤器上的 (然后您不需要转换回AsQueryable()),即

    if (p_Criteria.Titles.Any())
    {
        internetQuery = internetQuery
            .Where(c => p_Criteria.Titles.Contains(c.Title));
    }
    if (p_Criteria.MachineNamesAny())
    {
        internetQuery = internetQuery
            .Where(c => p_Criteria.MachineNames.Contains(c.MachineName));
    }
    if (p_Criteria.Severities.Any())
    {
        internetQuery = internetQuery
            .Where(c => p_Criteria.Severities.Contains(c.Severity));
    }

检查中的另一个问题是,通过在检查Take中运行.Count(),您正在具体化查询(如果您还没有这样做的话)。您应该Take()直接运行 - 无需检查我们是否超过了行数。如果行数少于p_Criteria.TopValue行数,则它将返回尽可能多的行数,即删除 if 检查并保留以下内容:

internetQuery = internetQuery.Take(p_Criteria.TopValue);

出于性能原因,我会考虑的另一件事是您是否可以将 FreeText 字符串检查更改为使用StartsWith而不是Contains. SQL 数据库 char 列上的索引仅在字符串的开头有效。如果%filter%不需要通配符,那么这显然与 OP 的代码不同,但将使用能够在FreeText列上使用索引:

if (!string.IsNullOrEmpty(p_Criteria.FreeText))
{
    internetQuery = internetQuery
        .Where(c => c.FormattedMessage.StartsWith(p_Criteria.FreeText));
}

小问题,不会影响数据库性能,但您可以将日期过滤的分支数量减少到以下:

if (p_Criteria.DateFrom != null && p_Criteria.DateFrom > DateTime.MinValue)
{
    internetQuery = internetQuery.Where(c => c.Timestamp >= p_Criteria.DateFrom);
}
if (p_Criteria.DateTo != null && p_Criteria.DateTo > DateTime.MinValue)
{
    internetQuery = internetQuery.Where(c => c.Timestamp < p_Criteria.DateTo);
}

从命名标准的角度来看,我还将您的 Object/DbContext 的名称从*ConnectionString更改为*Context.

于 2012-04-17T12:17:30.110 回答
1

由于具体架构不可用,您可以尝试以下操作。

  1. 使用所有过滤条件编写存储过程并从代码中发送参数。然后从代码中执行存储过程并检查是否仍然超时。要检查如何从实体框架调用 SP,请阅读

  2. 如果您在第 1 步中没有成功。您可能需要检查您的表设计并添加索引和/或额外的过滤器。要检查有关如何索引 SQL Server 数据库的指南,请阅读

  3. 您可能还想创建表的“影子”副本以保留存档的数据库行。存档的意思是到目前为止没有用的行,但不能永久删除。

编辑:我同意@Arion 关于使用分页网格而不是获取所有行的观点。

于 2012-04-17T12:13:51.053 回答
1

经过一周的解决方案搜索后,我找到了这篇文章。这适用于超过十亿行的索引数据库。这是我的代码解决方案:

public static IQueryable<LogViewer.EF.InternetEF.Log> ExecuteInternetGetLogsQuery(FilterCriteria p_Criteria, ref GridView p_Datagrid)
        {

            
            IQueryable<LogViewer.EF.InternetEF.Log> internetQuery = null;
            List<LogViewer.EF.InternetEF.Log> executedList = null;
            using (InternetDBConnectionString context = new InternetDBConnectionString())
            {
                internetQuery = context.Logs;
                if ((p_Criteria.DateTo != null && p_Criteria.DateFrom != null))
                {
                    internetQuery = internetQuery.Where(c => c.Timestamp >= p_Criteria.DateFrom.Value && c.Timestamp < p_Criteria.DateTo.Value);
                }
                else if (p_Criteria.DateFrom != null && p_Criteria.DateFrom > DateTime.MinValue)
                {
                    internetQuery = internetQuery.Where(c => c.Timestamp >= p_Criteria.DateFrom);
                }
                else if (p_Criteria.DateTo != null && p_Criteria.DateTo > DateTime.MinValue)
                {
                    internetQuery = internetQuery.Where(c => c.Timestamp < p_Criteria.DateTo);
                }
                if (!string.IsNullOrEmpty(p_Criteria.FreeText))
                {
                    internetQuery = internetQuery.Where(c => c.FormattedMessage.Contains(p_Criteria.FreeText));
                }

                
                if (p_Criteria.Titles.Count > 0)
                {
                    internetQuery = internetQuery.Where(BuildOrExpression<LogViewer.EF.InternetEF.Log, string>(p => p.Title, p_Criteria.Titles));
                }
                if (p_Criteria.MachineNames.Count > 0)
                {
                    internetQuery = internetQuery.Where(BuildOrExpression<LogViewer.EF.InternetEF.Log, string>(p => p.MachineName, p_Criteria.MachineNames));
                }
                if (p_Criteria.Severities.Count > 0)
                {
                    internetQuery = internetQuery.Where(BuildOrExpression<LogViewer.EF.InternetEF.Log, string>(p => p.Severity, p_Criteria.Severities));
                }


                internetQuery = internetQuery.Take(p_Criteria.TopValue);
                executedList = internetQuery.ToList<LogViewer.EF.InternetEF.Log>();
                executedList = executedList.OrderByDescending(c => c.LogID).ToList<LogViewer.EF.InternetEF.Log>(); ;

                p_Datagrid.DataSource = executedList;

                p_Datagrid.DataBind();


                return internetQuery;

            }
        }



public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>(
        Expression<Func<TElement, TValue>> valueSelector,
        IEnumerable<TValue> values )
        {
            if (null == valueSelector)
                throw new ArgumentNullException("valueSelector");

            if (null == values)
                throw new ArgumentNullException("values");

            ParameterExpression p = valueSelector.Parameters.Single();

            if (!values.Any())
                return e => false;

            var equals = values.Select(value =>
                (Expression)Expression.Equal(
                     valueSelector.Body,
                     Expression.Constant(
                         value,
                         typeof(TValue)
                     )
                )
            );

            var body = equals.Aggregate<Expression>(
                     (accumulate, equal) => Expression.Or(accumulate, equal)
             );

            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }

我希望这对我们的社区有用谢谢

于 2012-04-22T12:09:56.070 回答
0

问题是您试图将所有结果加载到您的数据网格中。真的有必要吗?您可以使用诸如分页之类的东西来仅读取第一行,例如 100 行,其余的仅按需读取吗?

于 2012-04-17T12:18:54.447 回答
0
internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.Severities.Contains(c.Severity))

EF 是否足够聪明,可以将这样的查询作为连接进行,还是会导致 SELECT N + 1 问题?

否则,您可能会丢失某些内容的索引。

我会先检查生成的 SQL。

于 2012-04-17T12:27:32.687 回答