7.5 KiB
AI 合同平台 V2 数据库搜索专用表改造闭环记录
日期:2026-06-24
结论
本轮完成 DB-5 第三阶段:统一搜索专用表 / tsvector 搜索列改造。
目标没有跑偏:本轮只处理合同系统统一搜索的数据库性能瓶颈,不改变前端主流程,不把 AI 中台检索逻辑塞回合同系统。合同系统仍然只维护合同、附件、审查任务这些业务读模型。
已经解决:
- 合同正文搜索不再直接跨
contracts.body_content做多字段OR + ILIKE。 - 审查任务搜索不再直接 join
review_tasks和contracts后全表过滤。 /api/v1/search前端接口保持不变,搜索结果 URL、分组、状态字段继续兼容。- PostgreSQL 环境已具备
search_vector、GIN 全文索引、trigram 索引。 - 旧库搜索表为空时会自动懒回填,避免迁移后必须手工执行一次重建任务。
本轮代码改造
新增迁移
新增:
yuqei-contract-platform/services/contract-api/migrations/versions/20260624_0009_add_contract_search_documents.py
新增表:
contract_search_documents
核心字段:
source_type:contract、file、reviewsource_id:来源业务 IDcontract_id:关联合同 IDtitlesubtitlesummarystatusstatus_labelupdated_atsearch_text- PostgreSQL 专用生成列:
search_vector
核心约束和索引:
ux_contract_search_documents_sourceix_contract_search_documents_type_updated_idix_contract_search_documents_status_updated_idix_contract_search_documents_contract_updated_idix_contract_search_documents_vectorix_contract_search_documents_text_trgmix_contract_search_documents_title_trgm
说明:
- SQLite 测试环境只创建普通表和 B-tree 索引。
- PostgreSQL 额外启用
pg_trgm,并创建tsvector生成列和 GIN 索引。 - 中文搜索同时保留 trigram 兜底,因为
simple分词对中文连续文本不一定能稳定切词。
仓储改造
修改:
yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/contracts.py
新增 ContractSearchDocumentModel,并在以下写入路径维护搜索读模型:
- 创建合同
- 修改合同
- Demo 数据 seed
- 上传附件
- 下载附件产生审计事件
- 发起审查
- 审查任务重新排队
- worker 领取审查任务
- 审查完成
- 审查失败
- 审批提交、退回、通过等状态流转
搜索逻辑由原来的三段跨业务表查询:
- contracts
- contract_files
- review_tasks + contracts
改为:
- 先查
contract_search_documents。 - 由
source_type生成兼容的SearchResult。 - 保留原有分组、状态、URL、排序语义。
API 状态挂载
修改:
yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/main.py
新增:
app.state.contract_repository = repository
用途:
- 方便测试和运行验证直接访问仓储实例。
- 不改变任何公开 HTTP API。
PostgreSQL 搜索 SQL 修复
远端 smoke API 验证首次暴露了一个 PostgreSQL 方言问题:
psycopg.errors.AmbiguousParameter: could not determine data type of parameter
原因:
原 SQL 写法为:
(:item_type IS NULL OR source_type = :item_type)
PostgreSQL 在参数同时参与 IS NULL 和等值比较时无法推断参数类型。
修复:
- 改为动态拼接过滤条件。
- 有
type时才加入source_type = :item_type。 - 有
status时才加入status = :status。
收益:
- 消除 500。
- SQL 更简单。
- 规划器更容易利用
source_type/status + updated_at组合索引。
验证结果
本地验证
已通过:
python -m compileall yuqei-contract-platform\services\contract-api\src\yuqei_contract_api yuqei-contract-platform\services\contract-api\migrationspython -m pytest yuqei-contract-platform\services\contract-api\tests -q
结果:
- 合同 API 测试:
24 passed - 空 SQLite Alembic 迁移链升级到:
20260624_0009 (head)
新增测试覆盖:
- 统一搜索能命中合同正文中的关键字。
- 统一搜索能命中附件。
- 统一搜索能命中审查任务。
- 审查任务完成后,回写的审查摘要能被统一搜索检索到。
远端 smoke 验证
执行:
yuqei-infra/scripts/deploy-v2-smoke-docker.ps1 -UseSshpass -AllowDirtyWorktree -SkipSmoke
通过记录:
project-docs/qa-artifacts/v2-smoke-docker-deploy/2026-06-24T19-26-51project-docs/qa-artifacts/v2-smoke-docker-deploy/2026-06-24T19-31-41
远端环境:
- Server:
192.168.3.90 - Compose project:
yuqei-contract-v2-smoke - Contract API:
http://192.168.3.90:18100 - Contract DB:
yuqei_contract
远端数据库检查:
- Contract API Alembic version:
20260624_0009 contract_search_documents.search_text:textcontract_search_documents.search_vector:tsvectorpg_trgm扩展存在- 搜索表索引存在:
ix_contract_search_documents_vectorix_contract_search_documents_text_trgmix_contract_search_documents_title_trgmix_contract_search_documents_type_updated_idix_contract_search_documents_status_updated_id
远端真实 API 链路
已验证:
- 创建合同。
- 使用正文关键字搜索合同。
- 发起审查任务。
- 使用审查关键词搜索 queued 审查任务。
结果:
body_search_hit = truereview_search_hit = true
验证样本:
contract-bc96db19d023review-747e59a2b937
远端 EXPLAIN 结果
合同正文搜索:
- 查询表:
contract_search_documents - 计划:
Index Scan Backward using ix_contract_search_documents_type_updated_id - Execution Time:约
0.273 ms - 未再扫描
contracts.body_content
审查任务搜索:
- 查询表:
contract_search_documents - 计划:
Index Scan Backward using ix_contract_search_documents_status_updated_id - Execution Time:约
0.166 ms - 未再扫描
review_tasks - 未再 join
contracts
说明:
当前 smoke 数据量较小,规划器优先使用 source_type/status + updated_at 组合索引再过滤关键字。大数据量下仍需继续观察 GIN search_vector / trigram 索引是否被稳定选择。
当前限制
本轮解决的是统一搜索主路径的全表扫描风险,不代表搜索系统已经最终完成。
仍需后续处理:
- 文件正文目前没有进入
contract_search_documents.search_text,只索引文件名、上传人、关联合同标题。后续等文件解析/OCR worker 稳定后,把解析文本写入同一张搜索表。 - PostgreSQL 中文全文分词仍采用
simple + trigram混合兜底,未来如需要更强中文分词,可评估专用中文分词扩展或应用层切词。 - 当前搜索表没有 tenant 字段。DB-2/DB-4 完成租户模型后,需要把
tenant_id加入搜索表、唯一约束和所有查询条件。 - 当前搜索表采用懒回填和同步写入,后续数据量变大后应增加显式“重建搜索索引”运维命令和后台任务。
下一步建议
DB-5 下一步建议进入:
pgvector 查询形态改造
目标:
- 让 AI Platform 的知识库向量检索稳定命中 HNSW 索引。
- 修复当前
embedding <=> query_vector查询形态没有命中表达式 HNSW 的问题。 - 在真实 PostgreSQL 上复测法律问答 / 合同审查 RAG 检索的 EXPLAIN。
之后再进入:
- DB-2:Contract Platform 清洁领域模型。
- DB-3:AI Platform 清洁领域模型。
- DB-4:tenant/RLS/权限隔离。
- DB-6:备份恢复、慢查询监控、数据库安全基线。