数据库中有数百万行,只需要这么多

Millions of rows in the database, only so much needed

问题总结:

问题:填充这些下拉列表的正确方法是什么?

现在了解更多详情。

--页面的目标--

用户会看到一些下拉列表,这些下拉列表在下面的网格中过滤数据。网格代表一个视图(参见 "Database"),其中结果被过滤。

每个下拉列表代表视图的一列的过滤器。一旦某些内容被 selected,页面的其余部分就会更新。其他下拉列表现在包含其对应列的可能值,这些值符合刚刚在第一个下拉列表中应用的过滤器。

一旦用户 select 编辑了几个过滤器,he/she 按下搜索按钮,下拉列表下方的网格就会更新。

--数据库--

我认为 select 几乎所有列都来自两个 table,没什么特别的。像这样:

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)。

table 上有几个索引,其中有 WHERE 子句的相关 ID。

重要提示:我无法更改数据库。也许在这里和那里设置一个索引,但没什么大不了的。

--Entity Framework--

我在我的解决方案中首先创建了一个数据库 EDMX 并映射了视图。 table 也有 类,但是我需要他们两个的数据,所以我不知道我是否需要它们。 select 从 table 中获取东西的问题是你不能应用一半的过滤,但也许有我还没有想到的聪明方法。

-- 查看--

我的视图与 viewModel 紧密绑定。在那里我为每个下拉列表都有一个 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 个日期列),对它们进行区分并将结果放入哈希集中。然后我将 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 的内存。这是 unacceptable,因为会有很多用户使用这个屏幕(很多会是...同时最多 10 个,平均 5 个)。

- 2 - 另一种解决方案是使用相同的 linq 查询并将 NameOfViewObjects 指向它生成的 IQueryable。这意味着每次视图想要将下拉列表绑定到 IEnumerable 时,它​​都会触发一个查询,该查询将在具有数百万行的 table 中找到该列的不同值,其中很可能是它正在获取值的列from 未编入索引。每个下拉列表(我有 10 个)大约需要 1 分钟,所以这需要很长时间。

别忘了:每次下拉列表中的一个 selection 发生变化时,我都需要更新下拉列表。

--问题-- 所以我可能采用了错误的方式,或者这些解决方案中的一个应该与索引我使用的所有列相结合,也许我应该使用另一种方式将数据存储在内存中,所以它只是一点点,但是一定有人以前做过这件事并想出了一些聪明的办法。你能告诉我处理这种情况的最佳方法是什么吗?

接受table 表现:

当然,您应该在 WHERE 子句中的所有列和组合上建立索引。没有索引意味着 table 次扫描和 O(N) 次查询。那些在任何情况下都无法扩展。

不需要下拉菜单中需要数百万个条目。您需要更聪明地过滤数据库,使其条目数可管理。

我会从 Google 获取一页。他们提前输入有助于将整个 Internet 图缩小到每页 25 或 50 个组,最有可能位于顶部。也许你也能做到。

也许更好的答案是搜索引擎之类的东西。如果您是 Java 开发人员,您可以尝试 Lucene/SOLR 和索引。我不知道 .NET 的等价物是什么。

您需要检查的第一点是您的数据库,确保您必须正确设置索引和实体关系,

下一步,如果您想动态构建过滤器选项,则需要 运行 使用现有过滤器进行查询,以获得下一个过滤器可能是什么。有几种方法可以做到这一点,

首先您可以查询数据并从 return 中提取值,这会占用大量的加载时间并浪费时间 return 处理您不需要的数据(除非您正在实时更新使用过滤器的结果并且没有分页,在这种情况下你也可以只获取所有数据并使用 linqToObjects 进行过滤)

第二个选项是对每个过滤器进行并行查询,returns 可能的过滤器,因此过滤器 A = 来自数据的所有可能的 A 值,过滤器 b = 过滤时 B 的所有可能值通过数据中的 A,C = 在数据中通过 A 和 B 过滤时 C 的所有可能值,等等。这比第一个好,但不是很多

另一种选择是使用聚合来加快速度,即您有一个如上所述的并行查询,而不是 returning 您 return 有多少条记录是 return ed,聚合函数总是更快,所以这将大大减少你的加载时间,但你仍然反复查询一个巨大的数据集,它不会很糟糕。 您可以使用 exist 进一步调整为 return a 0 或 1.

在这种情况下,您将查看具有所有可能过滤器的 table,然后从并行查询中删除没有值的过滤器

下一个最快的选择是在数据库中缓存过滤器,使用单独的 table 然后你可以查询它并从缓存中说,其中 filter = ABC select D,维护缓存的问题,你必须在数据库中作为保存功能,触发器等的一部分进行操作

我过去做过类似的事情 'kind of' - 如果您可以将 table 添加到数据库,那么我会探索引入 'scratchpad' 类型 table 当用户优化他们的搜索时,结果被临时存储。由于多个用户可能同时工作,table 必须有一个额外的列来识别用户。

我认为您会看到一些性能优势,因为所有处理都在服务器端进行,您的应用程序只需从此 table 中提取数据。由于您要添加此 table,因此您也可以完全控制它。

基本上我想程序流程会是这样的:

  1. 用户选择了一些过滤器并单击 'Search'。
  2. 服务器使用该搜索的结果填充暂存器 table。
  3. 应用从暂存器填充结果网格 table。
  4. 用户进一步细化搜索并点击 'Search'。
  5. 根据需要将服务器 removes/adds 行添加到暂存器 table。
  6. 应用从暂存器填充结果网格 table。
  7. 以此类推

与其让所有用户产生一个 'scratchpad' table,不如探索让每个用户临时 'scratchpad' tables。

除了前面的建议之外,可以添加的另一个解决方案是使用 /*+ result_cache */ 提示,前提是您的 Oracle 版本支持它 (Oracle version 11g or later)。如果查询的输出对于下拉列表来说足够小,那么当用户输入的条件与另一个用户使用的相同条件相匹配时,结果会在几毫秒内 returned 而不是几秒或分钟。结果缓存非常适合 return 数百万行中的一小部分行的查询。

select /*+ result_cache */ item_desc from some_table where item_id ...

当数据库表上发生任何 insert/updates/deletes 时,结果缓存会自动刷新。