PAYATHON 2026

React + Spring Boot 如何实现 45 列高基数级联筛选?

数据小陈

可以继续用 SQL 实现级联筛选。建议先按需查询候选值,限制单次返回量,并根据实际查询建立索引,再通过性能测量决定是否引入 Elasticsearch/OpenSearch。仅凭 45 个筛选列、每列约 10,000 个不同值,还无法判断 SQL 是否会成为瓶颈。

查询开销取决于数据表总行数、条件的选择性、需要扫描和去重的数据量,以及并发请求数。目前缺少这些信息,无法承诺某种架构的响应时间,也不能直接认定需要更换存储引擎。

先确定级联规则

你描述的是严格按顺序执行的级联:查询 column3 的候选值时,只应用 column1 和 column2 的已选条件。

按照这个规则,用户修改 column1 后,前端应清空后续选择及其候选列表,重新获取下一列的数据。也可以保留后续选择,由后端重新验证,但不能假定原有选择仍然有效。

如果实际需求允许用户任意选择 45 个字段,并让所有字段相互约束,筛选语义就不同了。此时需要明确,计算某个字段的候选值时,是否排除该字段自身的已选条件。两种语义会产生不同的查询和缓存键,应在实现前确定。

后端提供统一的候选值查询接口

不必为 45 列分别编写接口。可以用一个接口接收目标字段、已选条件和分页参数,由 Service 层校验,再由 Repository 层生成参数化 SQL。

下面是一个示意接口,并非 Spring Boot 内置 API:

POST /api/filter-options
Content-Type: application/json
{
  "targetField": "column3",
  "selected": {
    "column1": "A",
    "column2": "B"
  },
  "pageSize": 50,
  "cursor": null
}

响应可以采用:

{
  "values": ["C1", "C2"],
  "hasMore": false,
  "nextCursor": null
}

其中 50 只是示例页大小,服务端上限应结合交互需求设置。用户展开某个筛选项时再加载该字段,不必在每次条件变化时都计算全部 45 列。

动态查询需要处理两类输入:

  • 字段名通过服务端白名单映射为真实列名,同时校验字段顺序和允许使用的运算符。
  • 筛选值通过绑定参数传入,不能拼接到 SQL 字符串中。

列名通常不能用 ? 占位符绑定,因此字段需要白名单,筛选值也需要参数化。访问权限条件应由后端加入查询,不能依赖前端传入。

保留 SQL,并优化实际执行路径

查询 column3 的基本形式可以保持为:

SELECT DISTINCT column3
FROM data_table
WHERE column1 = ?
  AND column2 = ?
ORDER BY column3

如果业务不允许空值,可以额外加入 column3 IS NOT NULL;否则应明确空值如何展示和选择。

生产接口还需要添加所用数据库对应的分页或行数限制。例如每页展示 50 个值时,可以最多查询 51 个不同值,用多出的一个判断是否还有下一页。限制返回行数并不代表底层扫描量也受到同样限制,仍需查看执行计划。

对于频繁出现的上述查询,可以评估这样的复合索引:

CREATE INDEX idx_data_c1_c2_c3
ON data_table (column1, column2, column3);

前两列对应等值条件,第三列对应候选值。这个索引可能帮助数据库缩小扫描范围,降低部分取值或去重开销;实际效果取决于数据库实现、数据分布和执行计划。

不要直接建立一个包含全部 45 列的大索引,也不要给所有字段组合都建立索引。应优先优化请求量大、扫描成本高的查询,兼顾索引占用空间和写入维护成本。对于后续筛选列,可以评估包含部分有效条件和目标字段的索引,无须机械地把全部上游字段放进去。

条件增加也不一定让查询更慢。如果新增条件能显著缩小匹配范围,查询反而可能更快。应通过数据库的执行计划工具检查扫描行数、索引使用情况,以及排序和去重的成本。

高基数字段使用搜索和分页

每列有约 10,000 个候选值时,通常没有必要一次返回全部内容。可以使用支持输入搜索的下拉框,在已选条件范围内查询匹配的候选值。

搜索方式会影响索引选择。例如,前缀匹配与任意位置子串匹配的执行成本可能不同,具体能否使用索引取决于数据库、排序规则和索引类型。应先明确业务需要哪种匹配,再选择实现。

候选列表需要稳定排序。数据相对稳定、候选值适合比较时,可以考虑基于最后一个值继续查询的游标分页;如果使用 offset 分页,应测量深分页成本。数据在翻页期间发生变化时,列表仍可能变化,是否需要快照一致性取决于业务要求。

如果只展示前若干项,应提供“继续加载”或搜索入口,不能把截断后的列表当作完整候选集合。若界面不需要精确总数,也不必为每次请求附加一次 COUNT(DISTINCT ...)。

React 端控制请求频率和响应顺序

前端可以采取以下措施:

  • 对搜索输入做防抖,避免每次按键都发送请求。
  • 在筛选条件变化时取消旧请求,或将其标记为过期。
  • 只有响应对应当前筛选状态时,才更新候选列表。
  • 复用相同请求的缓存结果,并合并同时发生的重复请求。

取消请求不一定会终止服务器已经开始执行的 SQL,因此仍需检查响应是否对应当前状态。

下拉列表的虚拟滚动可以减轻 React 渲染压力,但不会减少数据库查询或网络传输量,需要与后端分页配合。

缓存优先覆盖重复出现的查询

候选值缓存适合数据变化较少、用户经常重复选择相同条件的场景。缓存键至少应包含目标字段、规范化后的筛选条件、搜索词和分页位置;有权限隔离时,还要包含租户或访问范围。

规范化时应固定条件顺序,并按业务语义处理值的顺序,避免语义相同的请求生成不同缓存键。缓存有效期和失效方式由可接受的数据延迟决定,可以采用 TTL、数据版本号或更新事件失效。

初始筛选项、常见上游组合通常更容易复用。深层筛选组合可能非常分散,应观察命中率,避免把大量几乎不会再次访问的结果长期留在缓存中。

预计算和物化视图适合稳定的热点路径

如果某些查询反复对大量重复数据做相同的去重操作,可以评估只保存所需字段组合的去重表,或数据库支持的物化视图。例如,某个热点查询只依赖 column1、column2 和 column3,就可以评估预先保存这三列的不同组合。

是否值得这样做,要比较预计算结果规模、查询收益和刷新成本。任意字段组合的数量会迅速增加,不适合预计算所有组合。即使是固定顺序级联,较长前缀的不同组合数量也可能很大。

还要明确刷新期间是否允许旧数据,以及如何处理新增、修改和删除。预计算会把部分开销转移到更新过程,仍有维护成本。

什么时候考虑 Elasticsearch/OpenSearch?

可以在以下条件下进一步评估搜索引擎:

  • 需求包含全文检索、相关性排序,且筛选与搜索紧密结合。
  • 任意字段组合的聚合请求很多,优化后的 SQL 仍无法达到延迟和吞吐目标。
  • 业务能够承担额外索引、数据同步和运维成本。

高基数候选值在搜索引擎中同样需要控制聚合开销和返回量。选型验证时应检查结果是否截断、能否遍历完整候选集合,以及数据库更新后多久能反映到搜索结果中。

如果主要需求是精确匹配、固定顺序级联,而且 SQL 已能满足目标,继续使用现有数据库会更容易维护。

建议如何验证方案?

先选取真实数据上的代表性路径:初始状态、低选择性条件、高选择性条件、深层级联,以及大量用户访问同一组条件的情况。记录端到端延迟、数据库查询耗时、扫描量、返回量和并发情况下的资源使用。

再根据瓶颈逐项处理:扫描量大就检查索引和查询形态,重复请求多就优化前端和缓存,去重成本高就评估预计算。如果测量仍显示现有方案无法满足目标,再比较搜索引擎等替代方案。

备注:内容仅供参考。