只读SQL代理:让DataGrip直连Yearning审核平台
在 Yearning 和 DataGrip 之间,我架了一个只读 SQL 代理。故事起因很简单:公司把生产数据库的入口统一收进了 Yearning,所有 SQL 查询必须走它的审核流程,这对权限管控和审计来说是好事,但真到了每天高频查数、翻数据、对账的时候,网页版查询页面的体验实在让人着急——补全基本靠脑子,结果集一多就转圈,快捷键形同虚设。我不想放弃 DataGrip 的补全、多 Tab 和历史记录,又不能绕过 Yearning 的管控直接连生产库。于是花了两三天做了个本地代理:DataGrip 连它,它把 SQL 解析一遍,只放行 SELECT/SHOW 这类只读语句,再通过 Yearning 的查询接口去执行,最后把结果集按 MySQL 协议返回给 DataGrip。查询体验回来了,审核日志也一条没少。这篇就完整拆一下这个代理的设计思路、核心实现和踩坑过程,给同样被"审核平台好用但查询端难用"困扰的朋友做个参考。
1. 为什么会有这样一只代理
1.1 Yearning 的价值和"体验天花板"
Yearning 这类 SQL 审核平台能在一家公司里落地,核心价值从来不是"查数据爽",而是"管得住"。它把数据库账号收归统一管理,开发人员不直接接触生产库密码,所有查询请求都能追溯到发起人,SQL 可以先审核后执行,敏感字段还能脱敏。这种东西在合规要求比较严的环境里几乎是刚需,不然审计起来连"谁在什么时候查过什么"都说不清。
但用久了你会发现,审核平台的查询端体验基本停留在"能用的网页"这个级别。我不否认它做了很多努力,可跟 DataGrip 这种深耕数据库 IDE 十几年的工具一比,差距是硬伤。比如写 SQL 时的智能补全,DataGrip 可以基于表结构和已有查询历史给出准确提示,Yearning 页面通常只能做到简单的关键字提示,遇到几十个字段的大表,效率差很多;比如结果集,DataGrip 可以直接在表格里筛选、排序、复制多行、导出 CSV,网页端对大数据量渲染很吃力,我遇到过几次 5 万行结果直接卡到浏览器无响应的情况。
1.2 真实痛点清单
我把自己和周围同事在 Yearning 查询页面上遇到的槽点整理了一下,大概有这么几类:
- 没有本地化的 SQL 补全和格式化,写多表关联时靠人肉记字段名,容易写错列名才发现。
- 结果集超出一定行数后页面明显变卡,翻页和筛选都难受。
- 查询历史不连续,换个科室换台电脑就找不回之前的 SQL。
- 快捷键几乎为零,不能像 IDE 里那样 Ctrl+Enter 执行、Ctrl+B 跳转表定义。
- 多表结构探查困难,想看看某个表的索引、外键、行数,要反复打开多个页面。
这些痛点单拎出来都能忍,但叠加起来就变成每天都要消耗大量时间的日常摩擦。
1.3 但我们不能直接绕过管控
这里要先把边界讲清楚:我做的代理不是用来绕过权限守卫的,也不是让谁偷偷直连生产库的。绕过 Yearning 的管控直接捣鼓数据库连接串,那是事故级别的问题,一旦出了问题,DBA 兜底的人和 SQL 发起人都会很难受。我的目标是"绕开查询页面的体验瓶颈",而不是"绕开安全管控"。所以这个代理的定位是:让 DataGrip 出现在查询链路的客户端位置,但真正的认证、权限判断、查询执行、审计记录全部还是由 Yearning 完成。代理本身反而多了一层强制只读的保护,你敢发一条 UPDATE,代理先给你顶回去。
那既然要保留管控,为什么非要搭一层代理?直接让 Yearning 开放一个"DataGrip 能连的端口"不就行了?问题在于 Yearning 的定位是审核平台,它的强项是流程和审计,不是做数据库协议网关。等它原生支持 IDE 直连,不如自己动手把这条链路串起来。
2. 方案对比:为什么最终选择代理
2.1 先说说我排除掉的几条路
动手写代码之前,我认真考虑过三个替代方案。
第一个方案是改 Yearning 源码,在它里面加一个数据库协议监听端口。这条路能打通,但后果是我们要长期维护一个 fork 分支,Yearning 主版本一升级,我们的改动可能要重新适配。公司的运维资源有限,我不想为了一个体验优化背上一个持续维护的包袱。如果 Yearning 是企业版也许可以提需求,但开源自建的情况下,改主干不现实。
第二个方案是看 DataGrip 有没有办法直接对接 HTTP API,比如通过 Database 插件写自定义数据源。DataGrip 的插件机制确实能扩展不少东西,但"把 HTTP JSON 结果映射成数据库表格并支持 IDE 的补全和结果集操作"这个工程量,对一个人来说太庞大了。而且很多内部接口没有文档,维护成本也不低。
第三个方案是给数据库账号做只读权限,然后让 DataGrip 直连。这是最省事的,但问题也很明显:只读权限能拦住写入,却拦不住失控的全表扫描,更留不下业务层面的审计记录。谁查了哪些敏感字段、跑了多久、拉了多少行,数据库端很难快速对接到现有审计流程。而且生产环境的网络策略通常不允许开发人员直接访问数据库端口,放开这条路等于开了一个新的攻击面。
2.2 只读代理方案的三个核心优势
最后确定的方案是在本地或者内网一台跳板机上跑一个轻量代理,用 MySQL 协议对外提供连接,DataGrip 把它当成一个普通的 MySQL 实例来连。代理收到 SQL 之后在本地做一次只读判断,合法的请求通过 Yearning 的查询接口执行,结果再转换成 MySQL 协议的数据包返回。
这个方案有三个好处。第一,客户端零改造,DataGrip 也好、其他支持 MySQL 驱动的工具也好,只要往代理地址一连就行,不用装插件、不用写扩展。第二,安全边界非常清晰,代理这一层既是协议网关,也是只读策略执行点,它从设计上就不提供写入能力,比单纯依赖数据库账号权限更稳。第三,审计链路不断,查询仍然由 Yearning 执行,所以 receiver 还是它,日志照常记录。
| 方案 | 客户端改造量 | 安全/审计完整性 | 维护成本 | 结论 |
|---|---|---|---|---|
| 改 Yearning 源码 | 无 | 完整 | 高,升级困难 | 放弃 |
| DataGrip 插件对接 HTTP | 高 | 依赖插件实现 | 高,需大量开发 | 放弃 |
| 数据库只读账号直连 | 无 | 弱,无业务审计 | 低 | 放弃 |
| 本地只读 SQL 代理 | 无 | 完整,且多一层只读强制 | 低,约 600 行代码 | 采用 |
2.3 这个代理到底是什么
用一句话说清楚它的身份:它是一台站在 DataGrip 和 Yearning 之间的"翻译官"。对外,它讲 MySQL 协议,让 DataGrip 以为连上了一台数据库;对内,它讲 HTTP,调用 Yearning 的查询能力。这个翻译过程里还夹带了一个私货——SQL 只读检测。任何非只读语句在代理这一层就直接返回错误,根本到不了 Yearning。这也意味着即使有人拿到了代理连接串,也做不了任何写入操作,这是一个额外兜底。
3. 代理的整体架构与核心原理
3.1 从连接口到查询结果的完整链路
整个代理的逻辑可以分为五层,这五层是后面写代码时的骨架:
- 连接层:监听本地端口,实现 MySQL Server 协议,处理握手、认证、SQL 查询请求。
- 会话管理层:维护连接对应的用户、登录态、查询历史上下文,以及代理持有的 Yearning token。
- SQL 合法性层:对客户端发来的每一条 SQL 做词法解析,识别语句类型,判断是否允许执行。
- 上游调用层:通过 HTTP 与 Yearning 交互,完成用户身份映射、查询提交、结果获取、错误返回。
- 结果编码层:把 Yearning 返回的行列结构转换成 MySQL 协议要求的二进制/文本结果集,返回给客户端。
这样分层的好处是每一块都可以单独测试。连接层和结果编码层是纯协议活,SQL 合法性层是纯解析活,上游调用层最容易受 Yearning 版本影响,单独隔离出来,后续适配不同版本时只改这一块就行。
3.2 只读执法的核心:SQL Parser
这一层是整个代理的灵魂。你可能会想,判断只读还不简单吗,拿字符串匹配一下开头是不是 SELECT 不就行了?实际上真没那么简单。
首先,SQL 可以写成 SELECT 前面带注释、带括号、甚至跨多行的形式,字符串前缀匹配很容易被绕过。其次,不是所有以 SELECT 开头的语句都是安全的,比如 SELECT ... FOR UPDATE 会加行锁,在事务里还可能拖垮数据库;SELECT ... INTO OUTFILE 会把查询结果写到服务器磁盘上,严格来说也是危险的。再比如 WITH 开头的 CTE 语句,后面可能跟着 UPDATE 或 DELETE,仅看第一个关键字根本发现不了。还有多语句注入,一条字符串里用分号分隔多条 SQL,前面是 SELECT 后面藏个 DROP,如果协议层不拦,光靠匹配第一个语句就危险了。
所以这里必须引入真正的 SQL 解析器,把语句解析成语法树,看它到底是什么类型的操作。Go 生态里可选的有 PingCAP 的 parser、vitess 的 sqlparser 等,我最后选了空闲维护还算活跃的 tidb/parser,因为它对 MySQL 方言支持得不错,我们这边正好也是 MySQL 兼容数据库。解析之后,我维护一个只读语句白名单,大致包括:
- SELECT 及其变体,但不包括 FOR UPDATE、INTO OUTFILE 这类副作用子句。
- SHOW、DESC、DESCRIBE、EXPLAIN 这类元信息查询。
- WITH 开头的 CTE,但需要递归检查最终的主语句是否在只读范围内。
- 事务控制语句直接不允许,比如 BEGIN、COMMIT。因为一旦开放事务,只读窗口内可能出现多个查询之间的隔离级别问题,而且 Start Transaction 意味着连接进入事务状态,对只读场景没有价值。
这样的白名单机制比黑名单更安全。黑名单永远在猜攻击者会用什么姿势,白名单则把允许执行的语句类型限定在一个极小集合里,剩下的全部拒绝。
3.3 用户映射和 token 生命周期
DataGrip 连接代理时,需要在连接配置里填用户名和密码,这对应用户其实对应的是 Yearning 里的某个账号。代理拿到这组用户名密码后,会先请求 Yearning 的登录接口换一个 JWT token。这个 token 在代理内存里缓存,过期或者遇到 401 响应的时候自动重新登录。连接的会话管理层面,我们会记录当前连接对应的 Yearning 用户名,这样 Yearning 那边记下的审计日志能准确追溯到操作者。
token 的缓存有一个细节:代理会把同一个用户的多个连接共享同一个 token,避免每次新建连接都去登录一次。我在本地维护一个小 map,key 是 username,value 是 token 加上过期时间。任何线程拿不到有效 token 时,就触发一次异步刷新。这么做是必要的,因为 DataGrip 开启多个查询 Tab 时会建立多个连接,每个连接都走登录流程的话,Yearning 那边的登录日志会刷屏,而且慢。
4. 核心模块的实现细节
4.1 握手与认证:让 DataGrip 认我们是一台数据库
我用的 Go 语言,协议实现基于 siddontang/go-mysql 的 server 包。这个库把 MySQL 协议很多繁琐的细节封装好了,比如 4.1 握手包、认证包、COM_QUERY 命令包。实现一个基本可用的 SQL 代理,只需要实现它定义的 Handler 接口,把 Callback 里的 SQL 转发给解析器,再把结果写回去就行。
握手流程有几个关键参数要注意。一是能力标志位,我们只宣告支持 CLIENT_PROTOCOL_41、CLIENT_SECURE_CONNECTION、CLIENT_MULTI_STATEMENTS 这个能力位,但实际在 SQL 层会拦截分号分割的多语句,所以多语句能力宣告出来反而是个诱导层,真正执行时我们还是逐个解析并拒绝任何第二条语句。二是字符集设置,我强制按 utf8mb4 处理,避免 DataGrip 客户端和 Yearning 服务端编码不一致导致中文乱码。三是权限校验,代理里保存了一份允许连接 Yearning 的账号白名单,DataGrip 里填的用户名如果不在白名单里,握手阶段就会被拒绝。
这里要特别说明:代理本身不检查 Yearning 登录密码是否正确,它把这个判断交给 Yearning 的登录接口。所以我们把 DataGrip 连接串里的密码原样传给 Yearning,成功与否取决于 Yearning 那边能否认证通过。这样的好处是密码不落盘,也不存在第二套账号体系。
4.2 SQL 解析与只读拦截:白名单策略的落地
每一条 SQL 到达代理后,我们做的处理顺序是:
- 先去掉字符串里的注释,并做简单的长度限制,超过 1MB 的 SQL 直接拒绝,防止异常超大请求。
- 用 parser 把 SQL 解析成 AST。解析失败的直接返回语法错误,这通常意味着语句不合法,没必要往上送。
- 根据 AST 根节点类型判断语句类别。如果不在只读白名单里,立刻返回错误,错误信息是 "query is not allowed in read-only mode"。
- 如果根节点是 SELECT,再检查有没有 FOR UPDATE、INTO OUTFILE、LOCK IN SHARE MODE 这类子句,有就拒绝。
- 如果根节点是 WITH,继续向下遍历 AST,找主查询语句类型,按白名单再判断一次。
- 全部通过后,把原始 SQL 原样交给上游调用层。
实际代码里大概就是这样的逻辑骨架:
这一段逻辑看似简单,但它挡住了我们日常里 99% 的误操作风险。记得有一次同事试着在 DataGrip 里执行一条 UPDATE,手滑没确认目标表,结果显示的立刻是"query is not allowed in read-only mode",他愣了一下才反应过来已经没有直连生产库的权限了,连声道还好有这层保护。
4.3 上游调用与结果回传:把 JSON 变成 Result Set
代理和 Yearning 之间的通信走 HTTP。启动时我们会从配置里读取 Yearning 的地址,比如 https://yearning.example.com。用户认证通过后,代理向 Yearning 的查询接口提交 SQL,请求里带上 token、数据库名、SQL 文本这些参数。Yearning 执行完查询后返回的是结构化数据,通常包括字段名列表和行数据。代理拿到这些数据后,需要按照 MySQL 协议格式重新编码,让 DataGrip 以为是数据库直接发来的结果集。
这个编码过程有几个容易踩的坑。第一是空值处理,MySQL 结果集里 NULL 必须有专门的状态位,不能直接写空字符串。第二是字段类型推导,Yearning 返回的 JSON 里字段类型信息可能不完整,我需要根据数据本身做启发式判断:能解析成整数的映射成 long,能解析成浮点数的映射成 double,能解析成时间的保持字符串格式,其余映射成字符串。这样做会有类型偏差的风险,但在查询场景下基本不影响展示。第三是分页,Yearning 返回大量结果时可能有行数上限,我这一步的应对会在后面性能章节单独讲。
结果编码层的工作量不大,但正确性要求极高,因为 MySQL 协议里的长度编码和字段数量错误会导致 DataGrip 直接报"Unexpected packet"或连接断开。开发时我准备了一些固定结果集做协议层面的单元测试,后来又用真实查询来回验证,确保每一种字段组合都能正确渲染。
5. 部署与 DataGrip 接入实操
5.1 代理的启动与配置
代理本身是一个单二进制文件,我用 systemd 或 docker 都可以跑起来。线上环境我建议监听 127.0.0.1 或者内网网卡,不要直接暴露公网。配置文件大概是这样的结构:
不同的 Yearning 版本 API 路径可能有差异,这里的配置项写法只是一个示例,你部署时要对照自己实例的实际接口路径。配置文件的优点是把版本差异隔离在配置层,升级 Yearning 时如果接口变了,只需要改路径映射而不动代码。
启动后,我们可以先拿命令行工具测一下链路是否通:
如果能够正常执行 SHOW DATABASES 和 SELECT 1,说明协议层和认证层都是通的。我一般在正式使用前会跑三条语句做冒烟测试:一条 SELECT、一条被拒绝的 INSERT、一条语法错误的 SQL,确认三种路径的表现都符合预期。
5.2 DataGrip 新建数据源的具体步骤
在 DataGrip 里添加数据源,驱动选择 MySQL 即可,不需要特殊的 JDBC 参数。主机填 127.0.0.1,端口填 3307,数据库名填你希望查询的逻辑库名。这里的逻辑库名会原样传给 Yearning,Yearning 会根据账号权限去解析这个库是否存在以及当前用户能不能访问。
注意 DataGrip 连接数据库时通常会默认执行一些元信息查询,比如 SELECT @@version、SHOW VARIABLES、SHOW TABLE STATUS 等。如果 Yearning 那边的 API 对这类系统查询支持不完整,可能出现连接成功但数据源树刷新不出表的情况。我的处理是在代理层内置几条系统查询的本地应答,不转发给 Yearning。比如 SELECT @@version 直接返回一个 MySQL 8.0 兼容版本号,SHOW VARIABLES 返回最基本的几项参数,保证 DataGrip 认为连接到了一个正常数据库。
这一步是让 DataGrip 顺畅工作的关键,没做之前数据源面板经常报"Unable to resolve database"之类的错误。你也可以根据自己的 Yearning 实际能力,把这部分配置成白名单模式,决定哪些系统查询走本地,哪些透传。
5.3 日常使用体验和结果集操作
连上之后,DataGrip 就把它当普通数据库用了。写 SQL 时补全来自 DataGrip 对表结构的缓存,所以第一次连接时最好触发一次同步,让 DataGrip 把库表结构拉下来。之后写多表 JOIN 就爽了,字段名不记得了直接看提示。查询结果出来以后,可以用 DataGrip 自带的筛选器、列排序、导出功能,结果集过大的时候用它的 Streaming 模式,按需加载前几万行。
我还把常用查询保存成了 DataGrip 的 Scratch 文件,配合快捷键一键执行。过去在 Yearning 页面要复制粘贴好几轮的重复操作,现在一条 Ctrl+Enter 就完成了,效率提升非常明显。整个使用过程 Yearning 的审计日志照常记录,每次查询都还能追溯到具体用户。
6. 压测与真实使用体验
6.1 并发和耗时表现
代理上线前我做了简单的压测,模拟 50 个并发连接,每个连接循环执行 SELECT COUNT(*) FROM big_table 这种轻量查询。结果代理本身的 CPU 占用在单核 30% 左右,平均每条查询耗时的增加在 10ms 以内。这个开销来自 SQL 解析和协议转换,对日常查询来说可以接受。真正的瓶颈还是在 Yearning 和底层数据库,代理本身不太会成为瓶颈。
不过要注意,HTTP 转发是有连接池的,我复用了 Go 的 http.Client,默认情况下对同一主机可以建立多个连接。这避免了每一条查询都重新建立 TCP 连接的开销。如果 Yearning 服务端对 HTTP 连接有配额,可以在配置里限制一下 MaxIdleConnsPerHost,防止代理发出过多连接把 Yearning 压到限流。
6.2 几个让我意外的问题
实际使用中我碰到了几个测试阶段没暴露的问题。第一个是时区,DataGrip 默认会用本地时区解释 DATETIME 类型,而 Yearning 返回的时间格式可能不带时区信息,导致某些时间字段在 IDE 里和网页端显示差了 8 小时。这个问题最终通过在代理层把时间字段统一补充时区标记解决了一半,另外一半需要跟 Yearning 侧确认它的时间返回格式。
第二个是超大字段。有一次查询结果里包含一个很大的 TEXT 字段,代理在结果编码时按字符串直接传输会导致整个查询包非常大,DataGrip 那边解析慢,甚至直接报内存溢出。后来我给代理加了一个可配置的字段大小上限,超过上限的字段截断并在显示上做标记,避免整个结果集被打崩。
第三个是数据库名匹配。DataGrip 连接时填的 database 名必须和 Yearning 侧配置的库名完全一致,有同事填了大小写不同的库名,结果 Yearning 返回权限不足。这类问题排查起来很快,但第一次遇到容易懵,我后面会整理成排查表。
6.3 与团队协作时的体验
代理上线后,我把它部署在开发环境的一台内网机器上,先给组里几个同事试用了两周。反馈普遍集中在"终于能好好写 SQL 了"和"原来结果集还能这样筛选"。团队里最受影响的是做数据分析的同事,他们每天要临时查各种业务表,过去一年在 Yearning 网页上点来点去的动作现在全都在 DataGrip 里完成了。
不过也有同事反馈,代理不支持某些 Yearning 页面特有的功能,比如在网页上直接看 SQL 审核工单的状态、查看脱敏规则的效果。这些功能依赖 Yearning 的前端能力,代理作为协议网关确实覆盖不到。我会建议大家把代理定位成"查询工具",审核流程相关的操作仍然去 Yearning 网页完成,两者各司其职,反而更清晰。
7. 常见问题与排查技巧实录
7.1 典型问题速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| DataGrip 连接超时 | 代理没启动或端口被防火墙挡了 | curl 测试端口连通性,检查 systemd 状态 |
| 连接成功后看不到任何表 | 系统元信息查询没走本地应答 | 检查代理日志,看 SHOW TABLES 相关请求是否被转发到了 Yearning |
| 执行 SELECT 报权限不足 | Yearning 账号没有该库表的查询权限 | 去 Yearning 后台调整权限,或者联系 DBA |
| 执行 INSERT 没反应 | 被只读拦截返回了错误 | 查看 DataGrip 的消息输出,会明确显示 query is not allowed |
| 时间字段差 8 小时 | 时区处理不一致 | 在 DataGrip 连接配置里调整 serverTimezone,或在代理层补时区标记 |
| 大 TEXT 字段导致结果集卡死 | 字段超过代理截断阈值 | 调大/调小字段大小上限,必要时让同事避免查询超大字段 |
| 频繁提示登录失败 | Yearning token 过期或刷新失败 | 看代理日志中 token 刷新时的 HTTP 状态码,确认账号密码是否有效 |
这张表贴在项目 README 里,同事遇到问题先照着查一遍,大幅减少了重复的"帮我看看"消息。
7.2 排查方法论:先分层,再定位
代理涉及三层系统,出了问题如果没头绪很容易乱。我的排查顺序是:先确认代理到 DataGrip 的链路是否正常,再确认代理到 Yearning 的 HTTP 链路是否正常,最后才怀疑 SQL 本身。
比如 DataGrip 报连接失败,我会先看代理日志里有没有握手日志。如果连接都到不了代理,那是网络或端口问题;如果到了代理但等待在登录阶段,可能是 Yearning 登录接口的问题;如果登录成功但执行语句时报错,那再把 SQL 原文在 Yearning 网页上执行一次,看是 SQL 本身语法问题还是 Yearning 查询接口返回的数据格式不兼容。这个分层思维非常重要,它把各种诡异问题快速收敛到某个子模块。
7.3 容易被忽略的三个坑
第一个坑是 HTTP 连接复用不当导致的内存增长。Go 的 http.Client 默认会保存空闲连接,如果代理长时间运行且连接的 Host 带有 DNS 变化,可能会有少量连接残留。部署后我加了一个定时器,定期清理空闲连接,并监控内存曲线。
第二个坑是日志输出太多把磁盘塞满。代理记录全量查询日志时,如果某个同事在跑一个大批量循环查询,日志文件能在一小时内涨几个 GB。后来我把默认日志级别调成 error,只有 SQL 被拦截时才输出 warning,正常的查询日志改成异步采样统计,避免日志本身成为新的运维负担。
第三个坑是 Yearning 返回的错误信息对用户不友好。比如权限不足的错误可能是英文的、带着内部 JSON 结构,直接返回给 DataGrip 会让用户一头雾水。我在代理层加了一个简单错误码转换,把 Yearning 返回的常见错误码映射成清晰的中文提示,同时保留原始错误码作为排查依据。这个小优化被同事夸了很多次。
8. 后续扩展与个人体会
8.1 这个代理还能往哪个方向长
写完第一版之后,我发现这个架构有很强的扩展性。比如可以加 SQL 白名单,不是为了只读,而是为某些账号指定只能查询特定的表;比如可以做结果集缓存,重复的查询在分钟级内直接返回缓存结果,减轻数据库压力;比如可以对接多个 Yearning 实例,代理层根据库名路由到不同的上游,很多子公司的场景会很需要这个;再比如可以在代理里做慢查询统计,把每个用户最耗时的 SQL 记录下来,反过来喂给 Yearning 做优化分析。这些功能基本都能在现有分层结构里以插件形式加入,不会动到协议层。
还有一个小方向是"自动补全常见查询模板"。团队内部经常要查"最近 24 小时新增用户数"这类固定口径的数据,每次都是复制同一段 SQL。代理可以在识别到库名和简单关键字时给出提示,甚至预置一个下拉模板列表,进一步减少重复劳动。不过这种功能要看团队需求,做之前先问有没有人真的需要。
8.2 我在实际使用中的几点体会
这个项目做下来,我最大的体会是:把一个"给人用"的系统改造成"给 IDE 用"的时候,协议适配层是最有意思也最磨人的部分。数据库协议看似简单,真要对齐各种客户端行为,工作量远远超过写几个接口。好在我把范围控制得很小,只做只读、只做 MySQL 协议、只兼容 DataGrip,这才在几天内把整个链路跑通。如果一开始就想做一个通用数据库网关,估计现在还躺在设计文档里。
如果你也想在 Yearning 和 DataGrip 之间搭这么一层,我给的建议是先花半天时间把 Yearning 的查询接口摸清楚,确认它能返回完整的字段类型信息,再看协议层要适配哪些客户端行为。剩下的编码反而快。安全边界一定要画清楚:只读就是只读,不要为了灵活性开放任何写操作入口,哪怕权限在 Yearning 那边已经控制得很严,多一层兜底永远不亏。
最后再说一个小技巧:DataGrip 连接时,可以在高级参数里把 connectTimeout 和 socketTimeout 都调大一些,因为走代理链路后查询耗时通常比直连多几十毫秒,默认超时时间太短可能在某些慢查询场景下误报超时。这个细节说明文档里不会写,但我实测下来非常有帮助。