
先说一个很常见的场景你们团队的日常数据工作流不是靠一个人连上数据库写临时 SQL而是集中在某个协作 SQL 工具里。有人在里面维护了 200 多条核心查询有人把定时任务配置好让指标每天自动推到群里或表格里。然后有一天产品方通知这个工具不再维护了服务要关停。项目标题里提到的 PopSQL 和 SeekWell就是这类产品。前者偏向团队协作 SQL 编辑后者偏向把查询和输出管道化。它们真的停止服务时用户手里剩下的不是一段段 SQL而是一堆“知道有、但不知道存在哪”的逻辑遗产。这时候最容易冒出来的想法是我干脆自己写一个替代品。这个想法不算离谱但真正落地时会发现替代的不是一个编辑器而是一整套把 SQL 变成团队资产的基础设施。这篇文章想聊的就是“关停事件之后如果要自建替代方案到底要考虑哪些问题”。1. 工具停服最大的坑不是 SQL 没了是工作流断档了很多团队会低估协作 SQL 工具里沉淀的内容量。表面上看它们只是比 Navicat、DataGrip 多了一个网页界面让同事可以一起写查询。但用得越久它越不像工具越像一个组织内部的数据语义仓库。1.1 PopSQL 和 SeekWell 真正承担的角色PopSQL 这类工具的核心能力不是把 SQL 编辑器搬到浏览器里而是让“查询”成为一个可以共享、评论、版本化、按团队组织的对象。它把一个人脑里的临时分析过程变成了团队可见的持续过程。SeekWell 这类工具虽然偏自动化方向但本质也是解决同一个问题SQL 的结果不能只停留在开发者的终端里它需要被转成别人能使用的产物比如表格、定时报表、消息通知。当这样两个工具同时出现关停风险用户面临的第一层冲击是操作层连不上数据库、找不到历史查询、定时任务中断。第二层冲击才是真正麻烦的团队里原来“打开工具就能看到某条指标口径”的习惯消失了。如果有人问“咱们的月活跃用户数是怎么定义的”答案不再是直接打开某个共享查询去看而是变成“稍等我去找一下当时的导出文件”。这个变化比看起来严重得多。因为团队 SQL 工具里保存的不只是 SQL 文本还有围绕查询形成的上下文这段查询是谁写的、什么时候改过、当时为什么 join 这个表、结果集是否经过业务验证。一旦这些上下文被拆散到个人电脑的导出文件里数据口径就会迅速退化。1.2 出问题的不只是查询而是连接的可持续性还有一个容易忽略的隐患是账号和连接的可持续性。PopSQL 和 SeekWell 这类工具往往会帮你管理数据库连接。这意味着团队成员不一定知道生产库的地址、账号、端口和权限体系长什么样。工具关停以后首要任务不是重建编辑器而是找回一套可靠的数据库访问方式。更麻烦的是权限设计。原来的协作工具可能已经有“只读成员”“管理员”“可创建定时任务”的角色区分。自建替代方案如果只做编辑器不考虑权限边界就会变成“谁都能连数据库、谁都能跑任意查询”。这在分析团队内部还可以接受一旦涉及生产库或者财务敏感库就是事故隐患。从工程角度看这属于典型的“隐性依赖”失效你以为自己依赖的是一个 UI实际上依赖的是连接管理、权限模型、历史记录和团队协作规范。2. 真正的替代品不是再写一个编辑器而是把查询重新变成团队资产和可运行管道很多人一开始构建替代品时会把注意力放在“编辑器交互”上自动补全、结果集表格、查询历史。这些功能当然能做出来但它们不是替代品的核心。真正要替代的是三样容易被忽略的东西保存的查询目录、可重复执行的流程、以及团队对结果的信任。2.1 先建一个“查询即实体”的数据模型我在给类似场景做设计时一般会建议先忘掉界面从数据模型开始。一个协作 SQL 工具要支撑长期使用底层至少要有一个保存查询的对象。这个对象不是普通文件它应该有名字、描述、负责人、标签、SQL 正文、关联数据源、创建时间、更新时间、依赖说明。代码层面可以简单理解成类似下面的结构CREATE TABLE saved_queries ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, description TEXT, owner_id BIGINT NOT NULL, sql_text TEXT NOT NULL, database_connection_id BIGINT NOT NULL, tags TEXT[], created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() );这只是最基础的结构。真正重要的不是这张表的字段而是它背后的设计判断SQL 不再散落在个人工具的历史记录里而是一个拥有元数据、归属关系和变更痕迹的实体。在此基础上再补一张执行记录表用来记录每次运行的时间、执行人、运行状态、返回行数、耗时和错误信息。这样才能回答一个很常见的问题“这条 SQL 上次跑是什么时候那个数据结果是谁跑出来的”2.2 执行和调度要分开设计避免做成“玩具 Cron”SeekWell 这类工具的价值很大一部分在定时运行和数据投递。如果自己构建替代品同一个坑很容易踩两次第一次是把定时调度做得太简单第二次是把调度和查询执行绑得太死。如果只是把定时任务做成一个循环读数据库的 Cron短期内看着挺合理但会遇到几个现实问题查询越跑越慢上一次任务还没跑完下一次已经触发了。数据库连接没有释放连接数被占满。某个查询在凌晨 3 点失败如果没有告警可能等到第二天早上才发现。需要手动重跑某一天的任务但没有保留任务实例的概念。工程上的解决办法不是去写一个高可用分布式调度系统而是把“执行一次查询”和“调度执行”拆成两层。查询可以手动运行也可以通过调度触发。调度触发时先生成一个任务实例再交给一个异步执行器。任务实例记录自己的状态、参数和结果之后无论重试还是查日志都有据可依。不少自建方案会把执行器做成简单的 HTTP 接口POST /queries/{queryId}/run { params: { start_date: 2024-01-01, end_date: 2024-12-31 } }接口返回一个任务 ID前端再轮询任务结果。这种设计比同步请求更稳妥因为不是所有查询都能在几秒内返回。数据库慢查询、超时、锁冲突都是常态。3. 四个容易卡住的工程细节比要不要用某个前端框架重要得多如果你已经决定要自建或者正在为团队搭建一个内部 SQL 平台请把注意力从“前端选型”挪开。3.1 数据库连接的权限边界该收多紧很多自建 SQL 工具只做了“填一个 JDBC 连接串然后在网页里执行 SQL”。这是最危险的做法。因为它把数据库账号直接暴露给了所有使用者。更合适的处理方式是把数据源当成一个独立配置项。每个数据源可以有多个连接账号每个账号有不同的权限范围默认给分析查询使用只读账号。生产库连接必须走独立的只读用户禁止 DDL。执行超时、最大返回行数、最大并发数都在数据源层面做限制。这不是复杂化而是对所有 SQL 工具的基本要求。哪怕是内部工具连接字符串也不该直接出现在页面代码或前端请求里。正确的路径是前端只提交 queryId 或 SQL后端统一读取数据源配置并执行。3.2 存储的 SQL 和业务参数怎么分离另一个容易翻车的地方是直接把用户输入拼进 SQL。即使是在内部工具里这种做法也有很大风险既可能是无意的错误也可能因为 SQL 片段格式引发问题。更可控的做法是支持参数占位符。比如在查询里写SELECT * FROM orders WHERE created_at {{start_date}} AND created_at {{end_date}}执行时由后端把参数替换成预编译 SQL 参数而不是拼字符串。这样既能保证数据库能够复用执行计划也能避免参数中出现非预期内容。3.3 日志和审计是长期维护的关键而非附加功能工具关停事件里用户最痛苦的是历史记录丢失。自建替代品如果不再记录审计日志就等于把同一个坑又挖了一遍。每次执行都应该记录谁执行的。用了哪个数据源。跑了什么 SQL。是否成功。返回规模。耗时。如果是定时任务对应的任务实例 ID 是什么。有这些记录至少能回答三类问题数据结果为什么和昨天不一样、这张表的数据是从哪份查询来的、生产事故发生时哪些定时任务在跑。3.4 需要想清楚回写场景而不是只做查询PopSQL、SeekWell 这类工具在后期被用户大量使用的场景不只是查询还包括回写把汇总结果写进表里把某些操作需要的数据同步到另外一个库。但如果自建方案一开始就把“写库”做成通用能力权限风险会成倍上升。更稳妥的做法是先支持“导出到结果表”这类受限场景用户只有对特定 schema 的写入权限后端限制只能写目标表不允许执行任意 DELETE 或者 UPDATE。4. 如果团队准备迁移到自建方案这是一个可以执行的路径假设团队确实决定自建替代品而且目标不是做研究是真的要长期使用那么上来就写代码通常是错误的。4.1 第一阶段盘点旧资产决定迁移范围第一周不要写代码。先把旧工具里的查询导出来整理成清单。每一条查询需要打标签仍然在用且是核心指标。偶尔用但逻辑还在参考。已经废弃可以删除。定时任务依赖必须优先迁移。做完这一步你会得到一个比想象中更有价值的产出数据口径清单。过去散落在工具里的知识被整理成了团队共识。注意导出 SQL 时记得检查是否包含了数据库账号、密码、密钥等敏感信息。很多协作工具会在连接串或文档里残留这类信息导出的文件如果直接进 Git会造成泄露。4.2 第二阶段先做仓库再做执行最后补编排很多自建项目失败是因为顺序不对。一开始就做定时调度结果查询本身的可靠性和可观测性还没解决出了问题根本分不清是 SQL 错了还是调度器忘了跑。我建议的顺序是先把“保存查询”做出来。让团队成员可以把核心 SQL 统一收拢到一个地方。再补手动执行能力。关键是记录执行历史确认不同数据源都能稳定跑通。然后才考虑定时执行。先用最基本的任务实例模型不追求复杂的依赖关系。最后再补数据输出层比如投递到表格、群机器人、Webhook 或者附件。第 4 步很容易被提前。很多团队觉得“没有报表推送相当于没替代”。但推送的前提是查询稳定查询稳定又依赖日志和执行记录这些都是一层一层垒上去的。4.3 第三阶段找一个月度重复流程做验收判断自建替代方案是否合格不需要同时迁移所有任务。挑一个每月都要跑的统计流程拿它完整跑一遍手动执行、查看结果、发现口径异常、修复 SQL、重新执行、记录变更、让业务方确认数据准确。这个过程会暴露大量问题SQL 里可能本来就有隐性依赖比如“先跑 A 表再跑 B 表”。不同人运行时有不同的编写习惯有人喜欢在查询里直接USE某个库。结果集的导出格式和原来工具生成的格式不一定一致。权限配置可能过紧或过松。如果这个月度流程能连续跑两三次基本可以认为替代方案是能用的。如果连一条核心查询都稳定跑不下来就先不要继续加功能。5. 什么情况下不该自建什么情况下适合自建技术圈有一个很常见的偏见好像“自己动手”就一定比“采购工具”更可控。其实对一个停服工具做替代重点不是证明自己能写代码而是用最低的长期成本维持工作流稳定。5.1 可以先考虑非自建方案如果团队只有三五个人使用的数据库种类单一而且大多数查询是临时分析没那么多定时任务那么自建一个平台的成本可能大于收益。可以先用更轻的方案替代把 SQL 收进 Git 仓库配一个简单的只读查询工具。定时报表交给数据库自带的事件调度器。团队数据字典用 Wiki 或文档维护。这些方案虽然比较原始但能支撑小团队的日常协作。它们最大的优点是没有“服务被关停”的风险因为所有东西都在自己控制范围内。5.2 什么情况下值得把自建当正式项目做当一个工具承载的已经不只是查询而是“多个团队依赖的数据服务”时自建就有价值了。判断条件大概是如果有 5 个以上的人每天依赖这套流程有 20 条以上定时任务在跑而且查询结果直接进入业务决策或下游系统那这批工作流确实值得专门治理。这类情况下合适的方案不一定是全部自己造也可以考虑在现有开源查询客户端的基础上二次开发或者用开源任务编排工具来托管定时执行同时只自建一个轻量查询目录层。关键判断是不要把已经熟悉的开源生态推倒重来也不要因为自己只会写 Python就低估了数据权限和调度可靠性的复杂度。可以这样分需要的功能轻量替代方式自建或二次开发方式存储完整查询Git 仓库 文档带元数据的服务端存储按人授权数据库账号各自管理应用层统一鉴权执行历史手动记录服务端全量日志定时执行数据库 Event / Cron任务实例失败重试输出推送脚本跑完后写文件支持表格、消息、Webhook 的平台这张表不是为了让你一上来就选右边。如果你想通了长期要投入多少人维护再决定往右走到哪一层。5.3 避免落入“功能蔓延”陷阱自建工具最常见的失控路径是功能越加越多先做查询再做监控然后加数据血缘最后还想做拖拽式图表。半年后原始问题没有彻底解决团队反而多了一个需要持续维护的大系统。克制的方法是明确回答一个问题这套替代方案是服务“SQL 能不能写好”的还是服务“SQL 结果能不能到达需要它的人那里”的根据项目标题里提到的两个工具大概率后者才是更痛的需求。所以构建替代品时真正优先级最高的是“可靠地把查询结果送达到该去的地方”而不是把 SQL 编辑器做得越来越像 IDE。哪怕最开始的编辑体验很朴素只要查询目录、权限、日志和输出通道是稳定的使用者就会留下来。6. 最后说一点关于工具生命周期的经验PopSQL 和 SeekWell 不是第一个停服的协作数据工具也不会是最后一个。工具会变但团队的工作流不会一夜之间重建。真正值得投入去迁移和治理的不是模仿某个具体产品的界面而是把“写 SQL”“保存 SQL”“运行 SQL”“传递结果”这四个环节变成团队自己的可复用资产。这也是为什么我会建议如果真要动手第一行代码应该花在保存查询的数据结构上而不是前端页面。因为查询目录、执行记录和调度状态才是超越工具生命周期的资产。一个替代方案能不能长期活下去往往不取决于它的功能列表有多全而取决于它能不能让自己变得“可迁移”。如果过两年你们发现这个自建平台也成了瓶颈能不能顺利把查询和数据源配置再导出去靠的就是一开始有没有留好审计日志和元数据。今天如果有团队正被停服折腾最值得做的事不是急着选型而是先把旧查询导出来盘点里面哪些是真正决定业务口径的资产。清单到手以后再决定是自己造一个替代品还是把它接到一个更通用的内部工作流里去都会从容很多。