从巨大的数据库表中读取数据

3
我将使用LINQ来访问我的数据库表。其中一些表可能会有(数十万)条记录。
使用类似以下语句从表中读取数据:
var records = db.Logs;

将会使应用程序非常缓慢。它会一次性加载所有记录。

我需要通过多个条件对结果进行筛选,例如:

if (UserID != null)
{
    records = records.Where(r => r.User == UserID);
}

if (UserIP != null)
{
    records = records.Where(r => r.IP   == UserIP);
}

问题在于我第一次从表中读取数据将会获取所有记录,会导致应用程序非常缓慢。
是否有办法在LINQ语句中设置条件(如if、switch),就像我们在SQL语句中所做的那样?
以下是旧逻辑的创建方式:
string sql = "SELECT * FROM Log WHERE 1=1";
if (UserID != null)
{
    sql += " AND User = '" + UserID + "'";
}

if (UserIP != null)
{
    sql += " AND IP = '" + UserIP + "'";
}

sqlCmd.query(sql);

你是否在单独的线程上运行此查询,并使用回调来获取结果? - Security Hound
2
@Hamid,你在使用什么ORM?Entity Framework?LINQ-to-SQL?这两个都会生成类似于你的“旧逻辑”的智能TSQL,除非采取一些措施来防止它,比如从IQueryable强制转换为IEnumerable或者使用ToList() - bzlm
@Hamid,如果可能的话,请启动SQL Profiler,这样您就可以看到服务器上实际运行的查询。这将教您很多关于LINQ、EF的知识,并允许您进行调整。 - Emond
8个回答

6

实际上,像这样的任务:

  var records = db.Logs;

并不会立即执行查询。它只是准备一个数据结构以供稍后使用。只有当代码需要数据时,查询才会被执行,并且任何where子句都会集成到查询中,防止它返回整张表。

完全可以这样做:

  var records = db.Logs;

  if (filter1) records = records.Where(r => r.Field1 == condition1);
  if (filter2) records = records.Where(r => r.Field2 == condition2);

这将执行一个查询,带有一个(有点)动态的where语句。但是,您应该定义正确的索引。

再次强调:除非你已经进行了测量来验证这一说法,否则不要将性能问题归咎于某个特定的语句:“var records = db.Logs;”已被证明是无辜的。 - Heinzi

2

我认为你是错误的。调用 db.Logs 应该返回一个 IQueryable - 这意味着查询只有在需要检索数据时才会执行。例如,当您访问 Log 类的属性或将集合转换为列表 .ToList() 时。


2

db.Logs是什么?它不是一个IEnumerable<T>或者IQueryable<T>吗?在Linq2SQL中,通常情况下查询只有在调用.ToArray.ToList时才会被执行,因此您可以先构建查询树。


1
最好使用分页,获取您想要的数据,然后获取另一页。

1
问题在于我的第一次读取表格将会获取所有记录,使得应用程序变得非常缓慢。
var records = db.Logs

不从表中读取。除了创建查询之外,您什么也没有做(这类似于创建SQL命令文本,而不将命令文本发送到数据库以执行)。打开允许您查看发送到数据库的SQL命令的功能,您将看到此行代码不会发送任何内容到数据库。实际上,这一行也不会:

records = records.Where(r => r.User == UserID);

这只是修改名为records(你应该真正称之为recordsQuery)的查询,在User上添加条件。只有在迭代查询时,它才会被发送到数据库执行。因此,要么

records.ToList();

或者

foreach(var record in records) {
    // something something
}

或者使用其他方式执行查询。

这是旧逻辑的创建方式:

希望不是这样。注入攻击你好!

一些表可能有(数十万)条记录。

数十万条记录算什么。


0

假设您正在使用SQL Server,数十万行远非庞大。

回到主题,如果您传递一个IQueryable<T>,那么每次枚举时都会执行SQL。如果您的旧代码是正确的,而新代码很慢,那么这很可能是您的问题。您可以通过调用ToList()将其转换为IEnumerable<T>并将所有数据放入内存中来避免这种情况。

最后,您可以通过调用.Skip(PageSize * PageIndex).Take(PageSize)对数据进行分页。


0

试试这个:

List<Records> records;

if (UserID != null)
{
    records = db.Logs.Where(r => r.User == UserID).ToList();
}
else
{
    records = db.Logs.ToList();
}

-1
如果您使用lambda表达式,查询将不会执行,直到您使用它,例如转换为列表或枚举所有数据。
var filtered = (from l in db.Logs
where l => l.User == UserID
select l).ToList();

请在发言之前先尝试,谢谢。如果您真的关心性能,请不要使用LINQ。 - gekowa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接