数据库中的数百万行,只需要这么多
Posted
技术标签:
【中文标题】数据库中的数百万行,只需要这么多【英文标题】:Millions of rows in the database, only so much needed 【发布时间】:2016-09-04 00:29:09 【问题描述】:问题总结:
C# (MVC)、实体框架 5.0 和 Oracle。 我在连接两个表的视图中有几百万行。 我需要使用 filter-posibilities 填充下拉列表。 这些下拉列表中的选项应反映实际内容 该列的视图,不同的。 我想在您选择某些内容时更新下拉列表,所以 新选项反映过滤的内容,防止您 避免选择会产生 0 个结果的东西。 很慢。问题:填充这些下拉列表的正确方法是什么?
现在了解更多详情。
-- 页面目标--
用户会看到一些下拉列表,这些下拉列表可以过滤下面网格中的数据。网格表示过滤结果的视图(参见“数据库”)。
每个下拉列表代表视图列的过滤器。一旦选择了某些内容,页面的其余部分就会更新。其他下拉列表现在包含其对应列的可能值,这些值符合刚刚在第一个下拉列表中应用的过滤器。
一旦用户选择了几个过滤器,他/她就会按下搜索按钮,下拉列表下方的网格会更新。
-- 数据库--
我有一个从两个表中选择几乎所有列的视图,没有什么特别的。像这样:
SELECT tbl1.blabla, tbl2.blabla etc etc
FROM table1 tbl1, table2 tbl2
WHERE bsl.bvz_id = bvz.id AND bsl.einddatum IS NULL;
共有 22 列。 13 个 VARCHARS(大部分很小,1 - 20,其中一个的大小为 2000!),6 个日期和 3 个数字(其中一个大小为 38,其中一个大小为 15,2)。
表上有几个索引,其中有 WHERE 子句的相关 ID。
要知道的重要一点:我无法更改数据库。也许在这里和那里设置一个索引,但没什么大不了的。
-- 实体框架--
我在我的解决方案中创建了一个数据库优先 EDMX,并映射了视图。这两个表也都有类,但我需要它们两个的数据,所以我不知道我是否需要它们。从任一表中选择内容的问题是您无法应用一半的过滤,但也许有一些我还没有想到的聪明方法。
-- 查看--
我的视图与视图模型紧密绑定。在那里,每个下拉列表都有一个 IEnumerable。这些的 getter 从名为 NameOfViewObjects 的单个 IEnumerable 中获取其数据。像这样:
public string SelectedColumn1 get; set;
private IEnumerable<SelectListItem> column1Options;
public IEnumerable<SelectListItem> Column1Options
get
if (column1Options == null)
column1Options= NameOfViewObjects.Select(item => item.Column1).Distinct()
.Select(item => new SelectListItem
Value = item,
Text = item,
Selected = item.Equals(SelectedColumn1, StringComparison.InvariantCultureIgnoreCase)
);
return column1Options;
我尝试过的两种解决方案是:
- 1 - 选择下拉列表所需的 linq 查询中的所有列(2000 varchar 不是其中之一,并且只有 2 个日期列),对它们执行不同的操作并将结果放入 Hashset。然后我设置 NameOfViewObjects 指向这个哈希集。我必须等待大约 2 分钟才能完成,但在那之后,填充下拉列表几乎是即时的(可能每个都需要一秒钟)。
model.Beslissingen = new HashSet<NameOfViewObject>(dbBes.NameOfViewObject
.DistinctBy(item => new
item.VarcharColumn1,
item.DateColumn1,
item.DateColumn2,
item.VarcharColumn2,
item.VarcharColumn3,
item.VarcharColumn4,
item.VarcharColumn5,
item.VarcharColumn6,
item.VarcharColumn7,
item.VarcharColumn8
)
);
这里最大的问题是对象 NameOfViewObject 可能非常大,即使在此处使用 distinct,导致不到 100.000 个结果,它仍然使用超过 500mb 的内存。这是不可接受的,因为会有很多用户使用这个屏幕(很多用户会...最多 10 个,同时平均 5 个)。
- 2 - 另一种解决方案是使用相同的 linq 查询并将 NameOfViewObjects 指向它产生的 IQueryable。这意味着每次视图想要将下拉列表绑定到 IEnumerable 时,它都会触发一个查询,该查询将在具有数百万行的表中找到该列的不同值,其中最有可能的是它从中获取值的列没有被索引.每个下拉列表(我有 10 个)大约需要 1 分钟,因此需要很长时间。
不要忘记:每次下拉列表的选择发生变化时,我都需要更新它。
-- 问题-- 所以我可能走错了路,或者也许这些解决方案之一应该与索引我使用的所有列相结合,也许我应该使用另一种方式将数据存储在内存中,所以它只是一点点,但是一定有人以前做过这件事并想出了一些聪明的办法。您能告诉我处理这种情况的最佳方法是什么吗?
可接受的性能:
页面加载时必须等待一段时间(2 分钟),但 之后一切都很快。 每次下拉列表都必须等待几秒钟 变化 页面使用的内存不超过 500mb【问题讨论】:
如果屏幕上有很多用户怎么办。该列表存储在客户端上。你所拥有的满足你可以接受的表现。 您要在服务器上运行它并为每个用户单独设置?我帮不了你。 @JonKoeter 正如其他人所说,我认为您需要重新审视您的用户界面设计。您也许可以考虑以自动完成方式使用 ajax 为下拉列表提供服务。 那还是需要查询整个表不是吗?还是我误解了你的建议? 当你想要大数据集的速度时,缓存是唯一的选择,你必须用存储来换取速度, 【参考方案1】:当然,您应该在 WHERE 子句中的所有列和组合上都有索引。没有索引意味着表扫描和 O(N) 查询时间。这些在任何情况下都无法扩展。
您确实不需要下拉列表中的数百万个条目。您需要更聪明地将数据库过滤到可管理的条目数。
我会从 Google 获取一个页面。他们前面的类型有助于将整个 Internet 图表缩小为每页 25 或 50 个的组,最有可能位于顶部。也许你也可以做到这一点。
也许更好的答案是搜索引擎。如果您是 Java 开发人员,您可能会尝试 Lucene/SOLR 和索引。我不知道 .NET 等价物是什么。
【讨论】:
如果我在下拉列表中使用的所有列上放置索引,您认为所有问题都会消失吗?我没有使用下拉列表中的所有数据,只是每列不同的数据。每个下拉列表剩下 3 - 15 个... 消失不,大概改善 没有。任何 GUI 都无法使用下拉列表中的数百万个条目。你需要更聪明地过滤。想想“提前输入”,而不是“选择”。索引是你的果酱。 searchengineland.com/… 也许这个 SO 问题可以提供帮助:***.com/questions/3670831/… 计算和存储这些索引会一次性命中,因此会对 CPU 和存储产生影响。当您插入或更新一行时,必须创建索引。就是这样。【参考方案2】:你需要检查的第一点是你的数据库,确保你必须正确的索引和实体关系,
接下来,如果您想动态构建过滤器选项,那么您需要使用现有过滤器运行查询以获得下一个过滤器可以是什么。有几种方法可以做到这一点,
首先,您可以查询数据并从返回值中提取值,这需要大量的加载时间并浪费时间返回您不想要的数据(除非您使用过滤器实时更新结果并且没有分页,在在这种情况下,您最好只获取所有数据并使用 linqToObjects 进行过滤)
第二个选项是对每个返回可能的过滤器的过滤器进行并行查询,因此过滤器 A = 数据中 A 的所有可能值,过滤器 b = 由数据中的 A 过滤时 B 的所有可能值,C = 在数据中由 A 和 B 过滤时 C 的所有可能值等。这比第一个要好,但不是很多
另一个选项是使用聚合来加快速度,即您有一个如上所述的并行查询,但不是返回返回多少记录的数据,聚合函数总是更快,因此这将大大缩短您的加载时间,但是你仍然在反复查询一个巨大的数据集,它不会是完全令人毛骨悚然的。 您可以使用 exists 进一步调整它以仅返回 0 或 1。
在这种情况下,您将查看包含所有可能过滤器的表,然后从并行查询中删除没有值的表
下一个最快的选项是将过滤器缓存在数据库中,并使用单独的表 然后你可以从缓存中查询并说,其中过滤器 = ABC 选择 D,这个维护缓存的问题,你必须在数据库中作为保存函数、触发器等的一部分来做。
【讨论】:
【参考方案3】:如果您的 Oracle 版本支持(Oracle version 11g or later),除了前面的建议之外,可以添加的另一个解决方案是使用 /*+ result_cache */
提示。如果查询的输出对于下拉列表来说足够小,那么当用户输入的条件与另一个用户使用的相同条件匹配时,结果会在几毫秒而不是几秒或几分钟内返回。结果缓存非常适合返回数百万行中的一小部分的查询。
select /*+ result_cache */ item_desc from some_table where item_id ...
当数据库表上发生任何插入/更新/删除时,结果缓存会自动刷新。
【讨论】:
好主意。不过,我将不得不添加一些SP。不确定这是否可行。我假设我不能单独通过 EF 应用它。不过,这看起来很整洁。谢谢。【参考方案4】:我过去做过一些“类似”的事情 - 如果您可以将表添加到数据库中,那么我会探索引入一个“便签本”类型的表,在用户优化搜索时临时存储结果。由于多个用户可能同时工作,因此该表必须有一个额外的列来识别用户。
我认为您会看到一些性能优势,因为所有处理都保留在服务器端,您的应用只会从该表中提取数据。由于您要添加此表,因此您也可以完全控制它。
基本上我会想象程序流程会是这样的:
-
用户选择一些过滤器并点击“搜索”。
服务器使用该搜索的结果填充暂存器表。
应用从暂存器表格中填充结果网格。
用户进一步细化搜索并点击“搜索”。
服务器根据需要删除/添加行到暂存器表。
应用从暂存器表格中填充结果网格。
等等。
与其让所有用户生成一个“便签本”表,不如探索为每个用户创建临时“便签本”表。
【讨论】:
我无法更改数据库。有没有办法从 webapp 中做到这一点? EF 会支持临时表吗? 抱歉,我希望您能够做出不影响现有数据库设计的更改!不幸的是(对你来说)我不相信实体框架支持临时表的概念。也许这会有用:link. EF 不会创建数据库临时表,但是一旦您实例化您的查询,您将拥有一个可以像临时表一样操作的数据的内存存储,但是一旦您离开数据库,您就会失去很多使用数据库操作大型数据集的性能提升 好点 @MikeT 指出性能损失,在这种情况下听起来很重要。以上是关于数据库中的数百万行,只需要这么多的主要内容,如果未能解决你的问题,请参考以下文章