Linq to Entity Paging with large dataset太慢了

Posted

技术标签:

【中文标题】Linq to Entity Paging with large dataset太慢了【英文标题】:Linq to Entity Paging With Large dataset too slow 【发布时间】:2014-02-04 08:48:21 【问题描述】:

我正在分析来自在线游戏的数百万场比赛的玩家数据。我正在尝试以块的形式将数据分页到内存中以减少加载时间,但是将 OrderBy 与 skip/take 一起使用需要的时间太长(即使是较小的查询也需要 20 多分钟)。

这是我的查询:

var playerMatches = (from p in context.PlayerMatchEntities
                     join m in context.MatchEntities 
                        on p.MatchId equals m.MatchId
                     where m.GameMode == (byte) gameMode
                        && m.LobbyType == (byte) lobbyType
                     select p)
                     .OrderBy(p => p.MatchId)
                     .Skip(page - 1 * pageSize)
                     .Take(pageSize)
                     .ToList();

MatchId 已编入索引。

每场比赛有 10 名玩家,我目前在 PlayerMatch 表中有 330 万场比赛和 3300 万行,但数据正在不断收集。

有没有办法解决 OrderBy 造成的性能大幅下降?

This post 类似,但似乎没有解决。

编辑:

这是生成的 SQL 查询:

SELECT
`Project1`.`AccountId`, 
`Project1`.`MatchId`, 
`Project1`.`PlayerSlot`, 
`Project1`.`HeroId`, 
`Project1`.`Item_0`, 
`Project1`.`Item_1`, 
`Project1`.`Item_2`, 
`Project1`.`Item_3`, 
`Project1`.`Item_4`, 
`Project1`.`Item_5`, 
`Project1`.`Kills`, 
`Project1`.`Deaths`, 
`Project1`.`Assists`, 
`Project1`.`LeaverStatus`, 
`Project1`.`Gold`, 
`Project1`.`GoldSpent`, 
`Project1`.`LastHits`, 
`Project1`.`Denies`, 
`Project1`.`GoldPerMin`, 
`Project1`.`XpPerMin`, 
`Project1`.`Level`, 
`Project1`.`HeroDamage`, 
`Project1`.`TowerDamage`, 
`Project1`.`HeroHealing`
FROM (SELECT
`Extent2`.`AccountId`, 
`Extent2`.`MatchId`, 
`Extent2`.`PlayerSlot`, 
`Extent2`.`HeroId`, 
`Extent2`.`Item_0`, 
`Extent2`.`Item_1`, 
`Extent2`.`Item_2`, 
`Extent2`.`Item_3`, 
`Extent2`.`Item_4`, 
`Extent2`.`Item_5`, 
`Extent2`.`Kills`, 
`Extent2`.`Deaths`, 
`Extent2`.`Assists`, 
`Extent2`.`LeaverStatus`, 
`Extent2`.`Gold`, 
`Extent2`.`GoldSpent`, 
`Extent2`.`LastHits`, 
`Extent2`.`Denies`, 
`Extent2`.`GoldPerMin`, 
`Extent2`.`XpPerMin`, 
`Extent2`.`Level`, 
`Extent2`.`HeroDamage`, 
`Extent2`.`TowerDamage`, 
`Extent2`.`HeroHealing`
FROM `match` AS `Extent1` INNER JOIN `playermatch` AS `Extent2` ON `Extent1`.`MatchId` = `Extent2`.`MatchId`
 WHERE ((`Extent1`.`GameMode`) = 2) AND ((`Extent1`.`LobbyType`) = 7)) AS `Project1`
 ORDER BY 
`Project1`.`MatchId` ASC LIMIT 0,1000

【问题讨论】:

【参考方案1】:

另一种方法是使用一个 VIEW 来执行连接并索引相应的列,然后创建一个使用 VIEW 并返回仅包含页面数据的 TABLE 的表值函数。 您必须手动编写分页的 SQL 查询,但我认为它会更快。 我还没有尝试过这样的事情,所以我不能确定会有很大的速度提升。

【讨论】:

【参考方案2】:

你没有提供足够的信息来帮助你,所以我会建议。 避免 order by 的一种方法是将行存储在已经按顺序排列的表中。我建议'MatchId' 是一个主键和MatchEntities 的聚集索引。这意味着 MatchEntities.MatchId 是按物理排序存储的。如果您切换连接流以先提取已排序的流,然后再提取附加流,则可以避免昂贵的排序。

像这样:

var playerMatches = (from m in context.MatchEntities     // note the switch: MatchEntities goes first
                 join p in context.PlayerMatchEntities
                    on p.MatchId equals m.MatchId
                 where m.GameMode == (byte) gameMode
                    && m.LobbyType == (byte) lobbyType
                 select p)
                 // .OrderBy(p => p.MatchId) // no need for this any more
                 .Skip(page - 1 * pageSize)
                 .Take(pageSize)
                 .ToList();

还可以查看查询计划以了解数据库如何执行查询、正在使用什么类型的连接等。也许您的原始查询根本没有利用排序。

【讨论】:

以上是关于Linq to Entity Paging with large dataset太慢了的主要内容,如果未能解决你的问题,请参考以下文章

LINQ to SQL语句之Top/Bottom和Paging和SqlMethods

Linq To Entity 查询条件扩展

初识Linq to Entity

linq to entity常用操作

LINQ to Entity 减去 2 个日期

什么是 Linq to Entity?