# 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`、`review` - `source_id`:来源业务 ID - `contract_id`:关联合同 ID - `title` - `subtitle` - `summary` - `status` - `status_label` - `updated_at` - `search_text` - PostgreSQL 专用生成列:`search_vector` 核心约束和索引: - `ux_contract_search_documents_source` - `ix_contract_search_documents_type_updated_id` - `ix_contract_search_documents_status_updated_id` - `ix_contract_search_documents_contract_updated_id` - `ix_contract_search_documents_vector` - `ix_contract_search_documents_text_trgm` - `ix_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 改为: 1. 先查 `contract_search_documents`。 2. 由 `source_type` 生成兼容的 `SearchResult`。 3. 保留原有分组、状态、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\migrations` - `python -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-51` - `project-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`:`text` - `contract_search_documents.search_vector`:`tsvector` - `pg_trgm` 扩展存在 - 搜索表索引存在: - `ix_contract_search_documents_vector` - `ix_contract_search_documents_text_trgm` - `ix_contract_search_documents_title_trgm` - `ix_contract_search_documents_type_updated_id` - `ix_contract_search_documents_status_updated_id` ### 远端真实 API 链路 已验证: 1. 创建合同。 2. 使用正文关键字搜索合同。 3. 发起审查任务。 4. 使用审查关键词搜索 queued 审查任务。 结果: - `body_search_hit = true` - `review_search_hit = true` 验证样本: - `contract-bc96db19d023` - `review-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:备份恢复、慢查询监控、数据库安全基线。