Linq:高性能数据库查询仅查询每个第 n 个元素

Posted

技术标签:

【中文标题】Linq:高性能数据库查询仅查询每个第 n 个元素【英文标题】:Linq: Performant database query only querying every nth element 【发布时间】:2019-12-08 17:07:09 【问题描述】:

我正在从事一个个人项目,在该项目中我需要一些关于对数据库进行高性能 Linq 查询的帮助。有问题的数据库可能有数百万个日志条目,并且通过 API (Asp),我希望可以选择仅将这些日志的代表性子集返回到图形界面。

这是有问题的方法:

public IEnumerable<Log> GetByParameter(int ParameterID,DateTime timeStart, DateTime timeEnd)
    
        return _context.Logs.Where
            (a => a.ParameterID == ParameterID && 
            (DateTime.Compare(a.LogDate,timeStart) > 0 && DateTime.Compare(a.LogDate,timeEnd) < 0)).ToList();
    

请注意,该方法接受两个 DateTimes 作为参数,这会产生应查询日志的时间范围。

我想像这样扩充这个方法:

public IEnumerable<Log> GetByParameter(int ParameterID,DateTime timeStart, DateTime timeEnd, int limit)

例如,给定传递的参数,数据库可能包含 200 万个条目,而 API 使用者的“限制”可能是 40000 个条目。因此:

numberOfEntries/limit = n

2*106 / 4*104 = 50

在此示例中,我希望将每 50 个元素返回给 API 的使用者,元素之间的时间间隔均匀。

一种简单的方法是在给定参数的情况下查询整个表,然后过滤掉,但这似乎很混乱,与这种方法有点对立,也可能非常无效。

所以这是我的问题:有没有办法编写一个查询,使它只查询每第 N 行的数据库?

提前致谢!

【问题讨论】:

你用的是什么数据库? SQL 服务器? 是否有理由限制您使用 LINQ (EF?)? @Oliver 并不特别,除了我编写的其他代码是使用 Linq 之外,我想学习语法。我可以访问数据库,也可以编写 SP Linq 有 Skip(i)Take(i)。有了它,你就可以分页了。 Here 对此有一个答案,您也可以在Queryable 上使用它。但是,您必须对其进行测试,因为它可能会在客户端执行。至于你的实际要求,我认为你不应该做你想做的事。您正在尝试任意下采样,这会导致许多其他问题。正确的做法是对结果进行分页。就性能而言,当您处理这么多行时,这主要取决于数据和索引等的性质,而不是 LINQ。 【参考方案1】:

您可以使用 SQL Server 窗口函数(如 row_number)来实现它:

WITH x AS
(
    SELECT ROW_NUMBER() over (order by LogDate) as rn, *
    FROM MyTable
    WHERE
        ParameterID = @ParameterID AND
        LogDate > @StartDate AND
        LogDate < @EndDate
)
SELECT * from X WHERE rn % 50 = 0

在 LINQ 中,您可以尝试使用以下子句:

var data = _context.Logs
    .Select((x, i) => new  Data = x, Number = i )
    .Where(x => x.Number % 50 == 0)
    .Select(x => x.Data);

但是要检查实际的执行计划,我估计不会是最优的。

不要忘记在 LogDate 上创建索引。

老实说,我不确定 SQL Server 是否是存储日志的好选择,我想使用 Elastic 之类的东西。

【讨论】:

【参考方案2】:

您可以采取的一种方法是在某种索引上使用模数。如果您已经有一个自动生成的Id,则可以使用它 - 但它并不理想,因为您不能依赖它是连续的。

您可以使用RANK() 在视图中创建索引列,但遗憾的是您不能直接在 EF 代码中使用 RANK()

类似于以下内容:

var interval = 5; 
return _context.Logs
    .Where(a => 
        a.ParameterID == ParameterID &&
        (
           DateTime.Compare(a.LogDate,timeStart) > 0 && 
           DateTime.Compare(a.LogDate,timeEnd) < 0) &&
        a.Id % interval == 0).ToList(); //Filter on modulus of an index

在这种情况下,我个人会用 SQL 编写查询。

【讨论】:

以上是关于Linq:高性能数据库查询仅查询每个第 n 个元素的主要内容,如果未能解决你的问题,请参考以下文章

如果语句仅选择 linq2xml 查询中的第一个值

(转)LINQ查询操作符之DistinctUnionConcatIntersectExcept

LINQ查询表达式 - LINQ 查询分组

使用 LINQ 通过一个查询从数组中获取前五个元素和后五个元素

LINQ查询操作符

EF Core 嵌套 Linq 选择导致 N + 1 个 SQL 查询