information-collection-s:Spring Boot 单表 80 万数据优化实战
招标公告信息采集系统从旧表 infor_notice 迁移到 tender_notice,引入 ES 全文搜索 + ngram/ik 分词器,精简接口响应字段,记录 4 个踩坑点和完整改造过程。
项目背景
information-collection-s 是一个招标公告信息采集系统,基于 Spring Boot 2.3.4 + Spring Cloud Hoxton.SR3 构建,服务于政府采购和招标信息的采集、检索和展示。系统承接的是原 infor_notice 单表(历史遗留),需要迁移到新表 tender_notice,并针对单表 80 万条数据量做全链路优化。
核心挑战是:在不中断服务的前提下,完成数据源切换、检索能力升级(引入 ES 全文搜索),并将接口响应字段精简到只返回业务需要的核心数据。
技术栈
| 组件 | 版本 | 用途 |
|---|---|---|
| Spring Boot | 2.3.4.RELEASE | 后端框架 |
| Spring Cloud | Hoxton.SR3 | 微服务治理 |
| Java | 1.8 | JDK |
| MyBatis | — | 数据访问层 |
| MySQL | — | 业务数据库 |
| Elasticsearch | RestHighLevelClient | 全文搜索 |
| Redis | Jedis 客户端 | 缓存 / 幂等 |
优化动机
原系统的问题很典型:单表 infor_notice 存量 80 万条数据,接口返回了将近 30 个字段,但前端真正用到的不到一半。大量无用的字段传输(content、styledContent、fileList、nodes、attrs 等)增加了网络开销和序列化成本。
同时,原系统的 ES 索引 information-collection-dev-s-file 只支持基础搜索,没有针对中文招标场景做分词优化,搜索准确性差。
核心改造
1. 数据源切换
将接口 /inforNotice/getNoticeList 的数据源从旧表 infor_notice 切换到新表 tender_notice:
| 场景 | 改造前 | 改造后 |
|---|---|---|
| 无关键字查询(查数据库) | infor_notice | tender_notice |
| 有关键字搜索(查 ES) | ES information-collection-dev-s-file | ES information-collection-dev-s |
2. ES 索引重设计
新建索引 information-collection-dev-s,针对招标公告场景做分词优化:
{
"title": "ngram_analyzer (支持模糊搜索)",
"noticeTypeName": "ik_max_word / ik_smart (中文分词)",
"realNoticeTypeName": "ik_max_word / ik_smart (中文分词)"
}
- title 字段 用 ngram 分词器实现模糊匹配,用户输入部分关键词也能命中
- noticeTypeName / realNoticeTypeName 用 ik 分词器做精确中文分词
3. 响应字段精简
接口返回从 28 个字段精简到 14 个。删除的字段包括:
content/styledContent→ 大文本内容,前端不展示fileList/url/localFilePath/localFileName→ 附件信息冗余author/agency/contactInformation/sourceName→ 招标场景不需要attrs/nodes/remark/searchLabel/label→ 历史遗留字段
同时新增 5 个业务字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| winnerAmount | Integer | 中标金额 |
| winnerCompany | String | 中标公司 |
| loserCompanies | String | 未中标公司(JSON 数组) |
| contactPerson | String | 联系人 |
| contactPhone | String | 联系电话 |
4. 数据同步机制
新增 /sync/tenderNoticeToEs 接口,支持两种同步模式:
- 全量同步(
fullSync=true):一次性将 MySQL 全表数据同步到 ES - 增量同步(默认):按
last_sync_time只同步新增/变更数据
单批 500 条,循环写入 ES。新增 tender_notice_sync_record 表记录每次同步的元信息(同步时间、同步条数、同步类型)。
当前存量数据:14,214 条(后续持续增长至 80 万+)。
踩坑记录
1. NGram 分词器差值错误
The difference between max_gram and min_gram in NGram Tokenizer
must be less than or equal to: [1] but was [9]
ES 7.x 默认 max_ngram_diff 上限为 1。当 min_gram=2, max_gram=11 时差值 9 超出限制。在索引设置中加上 "max_ngram_diff": "10" 解决。
2. ES 索引未自动创建
项目启动时 afterPropertiesSet() 只初始化了 InforNoticeES 的索引,没有初始化 TenderNoticeES。导致首次搜索时报 index_not_found_exception。在初始化方法中补充一行解决:
initDetail(TenderNoticeES.class);
3. 泛型类型推断失败
ES 查询返回的 BaseResponse 泛型擦除导致编译报错:
// 编译报错:不兼容的类型
return BaseResponse.success(data);
// 修复:明确指定泛型
return BaseResponse.<String>buildResponse()
.setCode(200)
.setMessage("请求成功");
改造结果
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 接口返回字段 | 28 个 | 14 个 |
| 检索方式 | 单数据库 LIKE | MySQL + ES 双引擎 |
| 中文搜索 | 不支持 | ngram 模糊 + ik 精确分词 |
| 数据源 | 旧表 infor_notice | 新表 tender_notice |
| ES 存量数据 | — | 14,214 条(持续增长) |
| 同步方式 | — | 全量 + 增量手动触发 |
总结
这次改造的核心思路是精简 + 分层:
- 接口只返回前端要的数据,不做无谓传输
- MySQL 兜底精确查询,ES 负责全文搜索,各司其职
- 数据同步机制设计为手动触发 + 增量模式,不给系统增加自动同步的运维负担
对于单表 80 万数据量来说,单纯靠 MySQL LIKE 迟早会扛不住。引入 ES 后,检索性能的瓶颈从数据库 IO 转移到了 ES 的倒排索引,配合 ngram 分词器还能实现模糊匹配,用户体验提升明显。
后续如果数据量继续增长到百万级,可以考虑将同步机制升级为监听 MySQL binlog 的准实时同步(Canal),而不是手动触发。