- 后端
- 数据库
- 负载均衡
【免费下载链接】proxysql
High-performance proxy for MySQL and PostgreSQL
导读
本文深入剖析 ProxySQL MCP(Model Context Protocol)插件中基于 SQLite FTS5 扩展的全文检索(Full Text Search, FTS)能力,介绍其如何让 AI Agent 在mcp_catalog.db中发现式 Schema(Discovery Schema)内,快速检索索引化的数据库元数据与 LLM 生成产物。读完本文,你将掌握fts_objects与fts_llm两张 FTS5 虚拟表的建表与索引策略、catalog_search与llm.search两个 MCP 工具的调用方式与返回结构,以及从静态采集到全文索引重建的完整实现链路。该功能当前状态为IMPLEMENTED(已实现并通过测试)。
一、功能背景与设计需求
ProxySQL MCP 的 Discovery 子系统负责对 MySQL / PostgreSQL 目标实例进行元数据采集(Harvest),并将采集结果沉淀到 SQLite 数据库mcp_catalog.db中。随着库表、列、外键、视图定义与 LLM 生成的 question template、note 等产物不断累积,Agent 需要一种比精确 SQL 匹配更快、更灵活的检索手段——即全文检索。
关联文档 FTS_Implementation_Plan.md 明确了该系统的五条核心需求,它们直接决定了后续的架构选型:
- 索引策略:支持可选的 WHERE 过滤条件,不采用增量更新,重索引时全量重建(full rebuild on reindex);
- 检索范围:由 Agent 自行决定是单表检索还是跨表检索;
- 存储:所有行全部建立索引,不做数量限制;
- 目录集成:FTS 与 catalog 交叉引用——Agent 先用 FTS 拿到 Top N 的对象 ID,再回查真实数据库获取详情;
- 使用场景:FTS 只是 Agent 工具包中的一员,与
catalog_get_object、run_sql_readonly等工具配合使用。
二、整体架构与组件分层
MCP Query Endpoint ↓ Query_Tool_Handler (routes tool calls) ↓ Discovery_Schema (manages FTS database) ↓ SQLite FTS5 (mcp_catalog.db)- MCP Query Endpoint:接收 MCP 客户端(Agent/LLM)发来的工具调用请求;
- Query_Tool_Handler:统一路由层,负责解析
tool_name与arguments,把llm.search等调用分发到对应的执行函数(见 Query_Tool_Handler.cpp);MySQL 相关的catalog_search等工具则由 MySQL_Tool_Handler.cpp 承载; - Discovery_Schema:发现式 Schema 管理器,掌握
mcp_catalog.db的读写锁、预编译语句与全部 FTS 方法(见 Discovery_Schema.h 与 Discovery_Schema.cpp); - SQLite FTS5:底层的全文索引引擎,存放于
mcp_catalog.db数据库内。
关于数据库位置:mcp_catalog.db的路径被硬编码为datadir/mcp_catalog.db,见 ProxySQL_MCP_Server.cpp 中std::string(GloVars.datadir) + "/mcp_catalog.db"的初始化逻辑,以保证目录数据库在多次运行间保持稳定。
数据库设计:集成而非独立
FTS 功能并未另起一个独立数据库,而是直接内建在已有的 Discovery Schema(mcp_catalog.db)中,因此不需要单独的配置变量来开启。两张 FTS5 虚拟表分别是:
fts_objects:对数据库对象的 FTS5 索引,采用contentless模式;fts_llm:对 LLM 生成产物的 FTS5 索引,直接内嵌内容(with content)。
三、FTS 表结构与建表实现
源码中的create_fts_tables()(见 Discovery_Schema.cpp L700-L725)实际执行的建表语句与规划文档略有差异,仓库实际实现如下:
-- fts_objects:contentless 模式,只存索引 token,不复制原始数据 CREATE VIRTUAL TABLE IF NOT EXISTS fts_objects USING fts5( object_key, schema_name, object_name, object_type, comment, columns_blob, definition_sql, tags , content='' , tokenize='unicode61 remove_diacritics 2' ); -- fts_llm:直接存储内容,支持全文搜索 CREATE VIRTUAL TABLE IF NOT EXISTS fts_llm USING fts5( kind, key, title, body, tags , tokenize='unicode61 remove_diacritics 2' );对比规划文档 FTS_Implementation_Plan.md 中的简化版定义(fts_objects(schema_name, object_name, object_type, content, content='', content_rowid='object_id')),可以观察到实现上的几个关键升级:
tokenize='unicode61 remove_diacritics 2':使用 Unicode61 分词器并移除变音符号,对多语言元数据(包括中文注释、带重音字符的注释)更友好;fts_objects增加了object_key、comment、columns_blob、definition_sql、tags等字段:将对象注释、列清单、建表 DDL 和快速画像标签一并纳入索引,显著扩大可命中面;fts_llm采用kind, key, title, body, tags五列结构:kind目前包含question_template与note两种类型。
若 FTS5 扩展未被启用,create_fts_tables()会记录proxy_error("Failed to create fts_objects FTS5 table - FTS5 may not be enabled\n")并返回 -1。
四、索引维护:全量重建策略
规划文档强调"不做增量更新,重索引时全量重建"。这条策略在源码中落实为Discovery_Schema::rebuild_fts_index(int run_id)(见 Discovery_Schema.cpp L1215-L1346):
- 先通过
sqlite_master检查fts_objects是否存在,若不存在则仅打印 warning 并返回 0(非致命,采集流程可以在无 FTS 的情况下继续); - 按
run_id清空该批次对应的 FTS 条目:DELETE FROM fts_objects WHERE object_key IN (SELECT schema_name || '.' || object_name FROM objects WHERE run_id = ...); - 从
objects表取回该 run 的全部对象(object_id, schema_name, object_name, object_type, object_comment, definition_sql); - 对每个对象:
- 从
columns表按ordinal_pos排序拼出列摘要(格式如column_name:data_type column_comment); - 从
profiles表读取profile_kind='table_quick'的profile_json,提取guessed_kind作为tags; - 组装
object_key = schema_name + "." + object_name,用预编译语句INSERT INTO fts_objects(...)写入索引。
- 从
触发时机在采集链路中非常清晰:Static_Harvester::run_full_harvest()的阶段注释明确列出"9. Rebuild FTS index"(见 Static_Harvester.cpp L1259-L1329),即元数据采集完成后、finish_run之前执行rebuild_fts_index();若重建失败则以 "Failed during FTS rebuild" 结束本次 run。
LLM 产物的自动索引
LLM 产物的索引采用写入即索引的方式,在 upsert 操作内同步完成:
- 新增 question template 时,
llm_question_templates表插入完成后立即执行INSERT INTO fts_llm(rowid, kind, key, title, body, tags) VALUES(?1, 'question_template', ...),以template_id作为 rowid(见 Discovery_Schema.cpp L2097); - 新增 note 时,
llm_notes表插入完成后立即执行INSERT INTO fts_llm(rowid, kind, key, title, body, tags) VALUES(?1, 'note', ...)(见 Discovery_Schema.cpp L2149)。
class Discovery_Schema对外暴露的 FTS 接口汇总如下(见 Discovery_Schema.h):
class Discovery_Schema { private: // FTS 方法 int create_fts_tables(); int rebuild_fts_index(int run_id); std::string fts_search(int run_id, const std::string& query, int limit, const std::string& object_type, const std::string& schema_name); std::string fts_search_llm(int run_id, const std::string& query, int limit, bool include_objects); int log_rag_search_fts(...); public: // FTS 在以下流程中自动维护: // - 对象插入(静态采集 static harvest) // - LLM 产物 upsert // - 目录重建操作 };五、搜索工具详解
5.1 catalog_search(跨对象 + LLM 产物检索)
参数
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
| query | string | 是 | FTS5 搜索查询串 |
| include_objects | boolean | 否 | 是否包含对象详细信息(默认 false) |
| object_limit | integer | 否 | include_objects=true 时最多返回的对象数(默认 50) |
返回示例
{ "success": true, "query": "customer order", "results": [ { "kind": "table", "key": "sales.orders", "schema_name": "sales", "object_name": "orders", "content": "orders table with columns: order_id, customer_id, order_date, total_amount", "rank": 0.5 } ] }开启include_objects=true后,每个结果还会附加details字段,包含object_id、object_type、row_count_estimate、has_primary_key、has_foreign_keys、has_time_column以及columns数组(每列含column_name、data_type、is_nullable、is_primary_key)——这些信息正是从 catalog 的objects、columns、indexes、foreign_keys表中按需回查得到的。
实现逻辑
- 分别对
fts_objects与fts_llm执行 FTS5 查询; - 合并结果并按相关性排序;
- 按需获取对象详细信息(跨表 join 到
objects/columns/indexes/foreign_keys); - 返回排序后的结果集。
在 MySQL 侧的具体实现位于 MySQL_Tool_Handler.cpp 的catalog_search(schema, query, kind, tags, limit, offset),其内部委托给catalog->search(...),再把schema/query/results封装为 JSON 返回。注意该文件中还包含另一组面向数据表内容的 FTS 工具(fts_index_table、fts_search、fts_list_indexes),它们与本文所述的对象元数据 FTS 是不同层次的能力:前者索引的是业务表行数据,后者索引的是数据库元数据,使用时注意区分。
5.2 llm.search(LLM 产物检索)
参数
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
| query | string | 是 | FTS5 搜索查询串 |
| type | string | 否 | 内容类型过滤("summary"、"relationship"、"domain"、"metric"、"note") |
| schema | string | 否 | 按 schema 过滤 |
| limit | integer | 否 | 最大返回条数(默认 10) |
返回示例
{ "success": true, "query": "customer segmentation", "results": [ { "kind": "domain", "key": "customer_segmentation", "content": "Customer segmentation based on purchase behavior and demographics", "rank": 0.8 } ] }实现逻辑
- 对
fts_llm执行 FTS5 查询; - 应用类型 / schema 过滤条件;
- 返回带内容与排序分数的结果。
仓库中llm.search的实际实现与规划文档略有演进。在 Query_Tool_Handler.cpp L1638-L1643 中,工具注册信息如下:
- 必填参数:
target_id、run_id; - 可选参数:
query(string)、limit(integer,默认 25)、include_objects(boolean); - 功能描述明确说明:对 question_template 类结果,会额外返回
example_sql、related_objects、template_json、confidence;include_objects=true且 query 非空时(搜索模式)会附加完整对象 schema 详情;query 为空时(列表模式)只返回模板,不附加对象,以避免超大响应。
路由执行在 L2609-L2637:先解析target_id与run_id,通过catalog->resolve_run_id()将 schema 名解析为 run_id,然后记录搜索日志log_llm_search(run_id, query, limit),最终调用catalog->fts_search_llm(run_id, query, limit, include_objects)并封装为成功响应。
底层fts_search_llm(见 Discovery_Schema.cpp L2165 起)的实现要点:
- query 为空时进入列表模式:
SELECT f.kind, f.key, f.title, f.body, 0.0 AS score ... FROM fts_llm f LEFT JOIN llm_question_templates qt ON CAST(f.key AS INT) = qt.template_id ORDER BY f.kind, f.title LIMIT ...; - query 非空时进入搜索模式:同样 LEFT JOIN
llm_question_templates,但追加WHERE f.fts_llm MATCH '<query>' ORDER BY score LIMIT ...,score即bm25(fts_llm)的相关性分值; - 当
include_objects=true且 query 非空时,会先从objects表建立object_name -> schema_name映射,再对每条结果的related_objects逐个调用get_object(run_id, -1, schema_name, name, true, false)拉取完整对象详情,拼装为objects_details数组附在结果上。
5.3 fts_search(对象元数据检索的底层入口)
Discovery_Schema::fts_search(run_id, query, limit, object_type, schema_name)(见 Discovery_Schema.cpp L1348-L1394)是对象检索的底层实现,展示了 FTS 与过滤条件的组合方式:
SELECT object_key, schema_name, object_name, object_type, tags, bm25(fts_objects) AS score FROM fts_objects WHERE fts_objects MATCH '<query>' [AND object_type = '<type>'] [AND schema_name = '<schema>'] ORDER BY score LIMIT <limit>;可见"可选 WHERE 过滤"这一需求正是通过拼接object_type、schema_name条件实现的。
六、SQLite 并发与错误处理模式
FTS 检索发生在 MCP 插件多线程环境中,Discovery Schema 通过读写锁保证一致性(见 Discovery_Schema.cpp 中 "SQLite Operations Pattern"):
db->wrlock(); // 写操作(索引写入) db->wrunlock(); db->rdlock(); // 读操作(搜索) db->rdunlock(); // 预编译语句 sqlite3_stmt* stmt = NULL; db->prepare_v2(sql, &stmt); (*proxy_sqlite3_bind_text)(stmt, 1, value.c_str(), -1, SQLITE_TRANSIENT); SAFE_SQLITE3_STEP2(stmt); (*proxy_sqlite3_finalize)(stmt);ProxySQL 对 sqlite3 API 的函数指针封装(proxy_sqlite3_*)保证了在跨发行版链接场景下调用的一致性。
错误处理遵循统一 JSON 约定:
json result; result["success"] = false; result["error"] = "Descriptive error message"; return result; // 日志 proxy_error("FTS error: %s\n", error_msg); proxy_info("FTS search completed: %zu results\n", result_count);在 Discovery_Schema.cpp 中,FTS 相关的失败路径均有对应日志:建表失败记录proxy_error;搜索执行失败时proxy_error("FTS search error: %s\n", error)并返回"[]"空数组;fts_objects表缺失时仅proxy_warning并跳过重建,保证采集主流程不因索引问题中断。
七、Agent 工作流示例
以下是规划文档给出的典型 Agent 编排流程(Python 伪代码),展示 FTS 与 catalog 的交叉引用:
# Agent 搜索相关对象 search_results = call_tool("catalog_search", { "query": "customer orders with high value", "include_objects": True, "object_limit": 20 }) # Agent 搜索 LLM 洞察 llm_results = call_tool("llm.search", { "query": "customer segmentation", "type": "domain" }) # Agent 用结果构建认知 for result in search_results["results"]: if result["kind"] == "table": # 获取详细表信息 table_details = call_tool("catalog_get_object", { "schema": result["schema_name"], "object": result["object_name"] })实际使用时,llm.search需要携带target_id与run_id(或可解析的 schema 名),例如:
llm_results = call_tool("llm.search", { "target_id": "my_mysql_target", "run_id": "3", "query": "customer segmentation", "limit": 10, "include_objects": True })对于 question_template 类命中,返回中会附带example_sql(可直接执行的示例 SQL)与confidence(模板置信度),Agent 可据此直接构造查询或继续深挖关联对象。
八、性能考量
- Contentless FTS:
fts_objects采用 contentless 索引(content=''),索引只保存 token 与行号映射,不复制原始元数据,显著降低存储开销、提升检索速度;对象原始数据仍保留在objects等普通表中,需要详情时按需回查; - 自动维护:采集完成自动全量重建,LLM 产物写入即索引,索引始终与 catalog 保持同步;
- BM25 相关性排序:排名使用 FTS5 内置的 bm25 算法,
fts_search与fts_search_llm均以bm25(...) AS score计算相关性并按ORDER BY score输出; - 分页:大结果集可自动分页(MySQL 侧
catalog_search接受limit/offset,底层fts_search/fts_search_llm均有LIMIT约束); - 非致命降级:FTS 表缺失或重建失败时采集流程照常进行,避免索引问题拖垮主链路。
九、测试与验证状态
规划文档列出的测试项全部标记为完成:对象 FTS 搜索、LLM 产物 FTS 搜索、合并排名搜索、详细信息获取、按内容类型过滤、按 schema 过滤、大目录性能、错误处理。
这些测试在单元测试 genai_discovery_schema_unit-t.cpp 中有直接对应:
test_rebuild_fts_index()(L778-L796):插入users、orders两张表及列后执行rebuild_fts_index(run_id),断言返回 0;test_fts_search()(L798-L824):插入带注释的对象("User accounts and authentication"、"Customer purchase orders"、"Payment transactions")后重建索引,分别搜索 "users" 与 "orders",断言至少返回 1 条结果,验证了注释文本可被 FTS5 命中;test_log_rag_search_fts()(L519-L524):验证 FTS 搜索日志记录函数返回 0。
此外,测试文件顶部注释还点出了被测能力包括rebuild_fts_index() / fts_search()与log_llm_search() / log_rag_search_fts()等,可作为后续阅读与回归验证的入口。
十、注意事项与总结
注意事项
- FTS5 依赖编译进 SQLite 的 FTS5 扩展;若建表失败,请检查 ProxySQL 构建时 SQLite 的编译选项;
fts_objects采用 contentless 模式,无法直接通过 FTS 表反查原始内容,对象详情必须回查objects/columns等表;fts_llm直接存储内容,支持对title、body的全文检索;key字段与llm_question_templates.template_id或llm_notes.note_id对应,可用CAST(f.key AS INT)进行 join;- 重建索引是按
run_id隔离的:DELETE ... WHERE object_key IN (SELECT schema_name || '.' || object_name FROM objects WHERE run_id = ...),多批次采集之间互不污染; llm.search空 query 为列表模式(不带对象详情),非空 query 为搜索模式(可附带对象详情),两者响应体积差异很大,Agent 应谨慎使用include_objects=true。
总结
ProxySQL MCP 的 FTS 能力以两张 FTS5 虚拟表为核心,遵循"全量重建 + 自动维护"的索引策略,把数据库对象元数据与 LLM 生成产物统一纳入可全文检索的范畴,并通过catalog_search、llm.search两个 MCP 工具暴露给 Agent。底层实现中 contentless 索引、BM25 排序、读写锁与错误降级机制的组合,使其既能支撑大规模目录检索,又能与catalog_get_object、run_sql_readonly等工具协同完成"先检索、再精查、后执行"的完整 Agent 工作流。相关实现与测试可分别在 Discovery_Schema.cpp、Query_Tool_Handler.cpp、MySQL_Tool_Handler.cpp、Static_Harvester.cpp 与 genai_discovery_schema_unit-t.cpp 中继续深入。
- 后端
- 数据库
- 负载均衡
【免费下载链接】proxysql
High-performance proxy for MySQL and PostgreSQL
相关推荐
ProxySQL MCP 全文检索(FTS)实战指南:基于 SQLite FTS5 的 MySQL 数据快速发现
ProxySQL MCP 全文检索(FTS)实战指南:基于 SQLite FTS5 的 MySQL 数据快速发现 本文围绕 ProxySQL GenAI 插件中
后端数据库负载均衡ProxySQL RAG 索引数据模型与摄取架构:SQLite 文档层、FTS5 与向量检索的落地设计
ProxySQL RAG 索引数据模型与摄取架构:SQLite 文档层、FTS5 与向量检索的落地设计 ProxySQL 在 MySQL/PostgreSQL
后端数据库负载均衡Mermaid Live Editor 新手指南:免费三步画好、预览、分享第一张图
Mermaid Live Editor 新手指南:免费三步画好、预览、分享第一张图 Mermaid Live Editor 是一款免费开源的在线图表编辑器,用
前端开发者工具数据可视化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考