
1. 为什么跨库巡检总在“连一个库改一次连接串”上翻车跨库巡检这件事听起来只是“把同一段 SQL 在多个库上跑一遍”真做起来却很容易变成体力活。你手上有三套实例、每套实例下面十几个业务库想统计每个库的表数量、最大表行数、最近一次备份时间、有没有异常增长的日志表。最直觉的做法是写一个脚本把连接串里的数据库名当变量循环替换、循环执行。问题是连接串一换连接池要重建库一多脚本跑到一半断了你根本不知道断在哪个库某个库权限不足直接抛异常整个循环就停了前面的结果也没落盘。我试过用 Python 拼连接串的方式做这件事二十多个库跑下来最耗时的不是查询本身而是反复建连和异常处理。后来换成“在一个能访问实例元数据的库里用游标把库名逐个取出来再动态执行巡检 SQL”整个流程才稳定下来。这就是 SQL 游标遍历所有数据库的思路游标负责“列出有哪些库”循环负责“逐个进去干活”异常处理负责“某个库挂了不影响其他库”。这里要先说清楚一个前提游标遍历所有数据库通常是在同一个实例内遍历sys.databases或information_schema.schemata这类系统视图。如果你要跨多个实例那得先有一个“实例清单”再对每个实例分别跑一遍游标脚本。本文的场景是多实例连接 单实例内游标逐库 异常跳过 结果汇总最后把巡检结果统一输出。适合谁看适合做 DBA、数据平台、后端运维的同学尤其是手上管着多套数据库、又不想为每个库单独写脚本的人。核心检索词就是 SQL 游标、跨库巡检、数据库遍历、元数据采集。下面我会先讲清楚 TaoToken 统一 Key 怎么接入再给可复制的游标脚本模板最后逐库执行验证把全库清单和状态输出一次跑通。需要提醒的是游标遍历所有数据库时sys.databases里会包含master、tempdb、model、msdb这些系统库。如果你只想巡检业务库记得用WHERE name NOT IN (...)过滤掉或者用命名前缀过滤比如WHERE name LIKE WHQJ%。这一步不做后面统计出来的表数量会被系统库污染。2. TaoToken 统一 Key 前置把多实例连接收敛成一套配置跨库巡检的第一个痛点是连接管理。多套实例、多个库如果每个库都配一套账号密码脚本里就会散落一堆凭据改一次密码要改十几个地方。TaoToken 在这里的作用是把模型调用和数据库巡检脚本的接入方式统一起来你拿一个 Key配一个 Base URL就能在脚本里用同一套配置去调用模型能力比如让模型帮你生成巡检 SQL、解释异常结果、汇总巡检报告。先拿 Key。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。Key 拿到后不要写死在脚本里放到环境变量里比如TAOTOKEN_API_KEY。Base URL 用 https://taotoken.net/api 注意这个地址不加 UTM 参数直接写就行。模型 ID 按你实际要用的填比如做代码生成和 SQL 解释选一个你账号下可用的模型即可。这里要强调三件套Base URL、API Key、Model ID缺一不可。很多接入失败不是 Key 错了而是 Base URL 多写了斜杠或者少了/api。如果你用的是 Claude Code 这类编码工具接入配置可以写成环境变量或者配置文件。下面给一个通用的环境变量写法Linux/macOS 下直接 exportWindows 下用 set 或者写进系统环境变量export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYsk-你的Key export TAOTOKEN_MODEL你的模型ID如果你用的是 Codex 的auth.json配置结构大致是这样注意路径和字段名要和你的工具版本一致{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: 你的模型ID }如果你用的是 Cline 或者带 MCP 的编辑器插件配置里同样要写全三件套。MCP 的配置文件通常是 JSON字段名可能是baseUrl、apiKey、model具体看你用的插件版本。这里不展开每个工具的细节核心就一句话Base URL 指向 https://taotoken.net/api Key 用你创建的那把Model ID 填你账号下可用的。为什么要先做这一步因为跨库巡检脚本里你可能会让模型帮你做两件事一是根据库名和表结构生成巡检 SQL二是把巡检结果汇总成可读报告。如果连接配置散落在脚本各处后面排障会很痛苦。统一 Key 之后脚本里只读环境变量换环境只改环境变量不动代码。还有一个实际好处TaoToken 的模型对话入口 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 可以用来验证 Key 是否可用。你先在对话页发一条消息确认能正常返回再去跑脚本。这样能把“Key 问题”和“脚本问题”分开排障效率高很多。3. 可复制配置游标脚本模板与统一 Key 配置片段这一节给可直接复制的配置和脚本。先给统一 Key 的配置片段再给 SQL 游标遍历所有数据库的模板。脚本以 SQL Server 的 T-SQL 为例因为sys.databases 游标的组合最典型其他数据库的游标语法类似改一下系统视图和循环语法即可。先看统一 Key 的配置片段。如果你用 Python 脚本调模型可以这样读环境变量import os BASE_URL os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) API_KEY os.environ.get(TAOTOKEN_API_KEY, ) MODEL_ID os.environ.get(TAOTOKEN_MODEL, ) assert API_KEY, 请先设置 TAOTOKEN_API_KEY assert MODEL_ID, 请先设置 TAOTOKEN_MODEL如果你用 TOML 配置文件比如某些 CLI 工具支持config.toml写法如下[provider] base_url https://taotoken.net/api api_key sk-你的Key model 你的模型ID注意配置文件里的 Key 不要提交到 Git。生产环境用环境变量注入本地调试可以用.env文件但.env要加进.gitignore。接下来是核心SQL 游标遍历所有数据库的模板。这个模板做三件事声明游标取库名、循环逐个库执行巡检 SQL、把结果插入汇总表。先建汇总表IF OBJECT_ID(dbo.DBInspectResult, U) IS NULL BEGIN CREATE TABLE dbo.DBInspectResult ( id INT IDENTITY(1,1) PRIMARY KEY, db_name NVARCHAR(128), table_count INT, total_rows BIGINT, inspect_time DATETIME DEFAULT GETDATE(), status NVARCHAR(50), err_msg NVARCHAR(4000) ); END然后是游标脚本主体。这里用动态 SQL 拼接因为要跨库查询必须用EXEC或者sp_executesqlSET NOCOUNT ON; DECLARE db_name NVARCHAR(128); DECLARE sql NVARCHAR(MAX); DECLARE table_count INT; DECLARE total_rows BIGINT; DECLARE db_cursor CURSOR LOCAL FAST_FORWARD FOR SELECT name FROM sys.databases WHERE name NOT IN (master, tempdb, model, msdb) AND state_desc ONLINE AND name LIKE WHQJ% ORDER BY name; OPEN db_cursor; FETCH NEXT FROM db_cursor INTO db_name; WHILE FETCH_STATUS 0 BEGIN BEGIN TRY SET sql N SELECT tc COUNT(*), tr ISNULL(SUM(p.rows), 0) FROM [ db_name N].sys.tables t JOIN [ db_name N].sys.partitions p ON t.object_id p.object_id AND p.index_id IN (0,1) WHERE t.is_ms_shipped 0;; EXEC sp_executesql sql, Ntc INT OUTPUT, tr BIGINT OUTPUT, tc table_count OUTPUT, tr total_rows OUTPUT; INSERT INTO dbo.DBInspectResult (db_name, table_count, total_rows, status) VALUES (db_name, table_count, total_rows, OK); END TRY BEGIN CATCH INSERT INTO dbo.DBInspectResult (db_name, status, err_msg) VALUES (db_name, ERROR, ERROR_MESSAGE()); END CATCH; FETCH NEXT FROM db_cursor INTO db_name; END CLOSE db_cursor; DEALLOCATE db_cursor;这段脚本的关键点CURSOR LOCAL FAST_FORWARD表示只读、只进、本地游标性能比默认游标好TRY...CATCH保证某个库权限不足或离线时循环不会中断错误信息落到err_msgsys.partitions里index_id IN (0,1)对应堆表和聚集索引SUM(p.rows)才是比较准的行数估算。如果你要跨多个实例就把这段脚本包一层“实例循环”。实例清单可以放在一张配置表里比如dbo.InstanceList字段是instance_name、conn_str。然后用sqlcmd或者 Python 的pyodbc对每个实例执行上面的游标脚本。这里不展开跨实例的完整代码核心是实例循环在外层库游标在内层两层都做异常捕获。配置片段和脚本模板都给全了。下一步是逐库执行验证看结果表里是不是每个库都有一行状态是不是 OK。4. 逐库执行验证从全库清单到状态输出一次跑通脚本写好了别急着一次性跑全量。先做小范围验证确认游标能正确取到库名、动态 SQL 能执行、结果能落表。验证分三步先看游标取到的库清单再单库试跑最后全量跑并检查结果。第一步只看游标取到的库名不执行巡检 SQL。把游标里的SELECT name单独拿出来跑SELECT name, state_desc, recovery_model_desc FROM sys.databases WHERE name NOT IN (master, tempdb, model, msdb) AND state_desc ONLINE AND name LIKE WHQJ% ORDER BY name;这一步确认三件事库名列表是不是你预期的、有没有库处于OFFLINE或RESTORING状态、命名前缀过滤对不对。如果这里多出或少掉库后面巡检结果一定不对。我踩过的坑是有个库处于RECOVERY_PENDING游标取到了但动态 SQL 执行时报错后来在WHERE里加了state_desc ONLINE才稳定。第二步单库试跑。把游标脚本里的db_name手动赋值为一个具体库比如WHQJ_Game01然后执行动态 SQL 部分看table_count和total_rows有没有值DECLARE db_name NVARCHAR(128) NWHQJ_Game01; DECLARE sql NVARCHAR(MAX); DECLARE table_count INT; DECLARE total_rows BIGINT; SET sql N SELECT tc COUNT(*), tr ISNULL(SUM(p.rows), 0) FROM [ db_name N].sys.tables t JOIN [ db_name N].sys.partitions p ON t.object_id p.object_id AND p.index_id IN (0,1) WHERE t.is_ms_shipped 0;; EXEC sp_executesql sql, Ntc INT OUTPUT, tr BIGINT OUTPUT, tc table_count OUTPUT, tr total_rows OUTPUT; SELECT db_name AS db_name, table_count AS table_count, total_rows AS total_rows;如果这一步报“对象名无效”或者“权限不足”说明当前登录账号对目标库没有VIEW DEFINITION或SELECT权限。解决办法是给巡检账号授予目标库的db_datareader角色或者单独授SELECT ON SCHEMA::sys。注意不要用 sa 跑巡检脚本权限太大风险高。第三步全量跑游标脚本然后查结果表SELECT db_name, table_count, total_rows, status, err_msg, inspect_time FROM dbo.DBInspectResult ORDER BY inspect_time DESC, db_name;预期结果是每个符合条件的库都有一行status大部分是OK个别权限不足的库是ERROR且err_msg里有具体原因。如果结果表里只有一行说明游标没循环起来检查FETCH NEXT是不是漏了或者FETCH_STATUS判断写反了。如果结果表里库名重复说明游标没DEALLOCATE重复执行时旧游标还在。验证通过后你可以把巡检结果导出成 CSV或者让模型帮你汇总。比如把结果表的前 50 行贴到模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 让它按“表数量异常”“行数增长异常”“错误库”分类。这一步不是必须的但能省掉手工看结果的时间。如果你要长期跑这个巡检建议把脚本做成定时任务比如 SQL Server Agent 的 Job每天凌晨跑一次结果表保留最近 30 天。长期编码和 Agent 场景可以看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 把巡检脚本和模型汇总串成自动化流程。5. 常见错排查401、local proxy failed、reading choices、OAuth 对照跨库巡检脚本本身和模型接入是两条线但排障时经常混在一起。这一节把常见报错按“脚本侧”和“接入侧”分开对照真实报错给排查路径。先看接入侧。第一个高频报错是401 Unauthorized。原因通常是 Key 没设置、Key 写错、或者环境变量没生效。排查顺序先确认TAOTOKEN_API_KEY在当前 shell 里能echo出来再确认 Base URL 是 https://taotoken.net/api 没有多余斜杠最后去 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 确认 Key 没过期、没被删。注意Key 只在创建时显示一次如果忘了就重新建一把。第二个报错是local proxy failed。这个通常出现在你本地配了代理但代理没启动或者端口不对。排查检查环境变量HTTP_PROXY、HTTPS_PROXY是不是指向了一个不存在的端口如果不需要代理直接unset掉。注意这里说的是本地网络配置问题不是让你去用什么特殊网络工具企业内网环境按公司规范配置即可。第三个报错是reading choices相关比如error reading choices: unexpected end of JSON input。这通常是响应体不是预期 JSON可能是 Base URL 指错了或者模型 ID 不存在。排查先用 curl 直接打一次接口看返回体长什么样curl -s -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:你的模型ID,messages:[{role:user,content:ping}]}如果返回的是 HTML 而不是 JSON说明 URL 路径不对。如果返回model not found说明 Model ID 填错了。这一步能把“网络问题”和“参数问题”分开。第四个是OAuth相关报错。如果你用的工具走 OAuth 流程报错可能是OAuth token expired或invalid_grant。排查重新走一遍授权流程确认回调地址和工具里配的一致。如果你用的是 API Key 模式就不会有 OAuth 问题所以能不用 OAuth 就不用Key 模式更直接。再看脚本侧。第一个常见错是游标未关闭表现为重复执行脚本时报“游标已存在”。解决办法在脚本开头加IF CURSOR_STATUS(global,db_cursor) -1 DEALLOCATE db_cursor;或者每次执行前手动DEALLOCATE。第二个错是动态 SQL 拼接注入风险虽然库名来自sys.databases相对可信但仍建议用QUOTENAME(db_name)包一层SET sql NSELECT tc COUNT(*) FROM QUOTENAME(db_name) N.sys.tables;;第三个错是权限不足导致整个循环中断。如果你忘了写TRY...CATCH一个库报错后面所有库都不跑了。加上TRY...CATCH后错误落到err_msg循环继续。第四个错是结果表字段长度不够err_msg用NVARCHAR(4000)如果错误信息超长会被截断排查时看不到完整原因。可以改成NVARCHAR(MAX)。还有一个容易忽略的点sys.partitions的rows是估算值不是精确值。如果你要精确行数得对每个表COUNT(*)但那会非常慢。巡检场景用估算值就够了别为了精确把脚本跑成几小时。排障时记住一个原则先确认接入侧三件套Base URL、Key、Model ID没问题再查脚本侧游标和动态 SQL。接入侧问题去 API Keys 和接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 对照脚本侧问题看错误信息和结果表里的err_msg。6. 把巡检脚本接进日常从手动跑到定时汇总脚本跑通一次不难难的是让它稳定地跑下去。这一节说几个实际经验帮你把跨库巡检从“手动执行”变成“日常可依赖”。第一把游标脚本存成存储过程比如dbo.usp_InspectAllDatabases参数是库名前缀和是否包含系统库。这样调用方只需要EXEC dbo.usp_InspectAllDatabases prefix WHQJ%;不用每次贴一大段脚本。存储过程里记得加SET NOCOUNT ON避免多余的结果集干扰调用方。第二结果表加分区或者按天清理。巡检结果每天一行每库一个月就是几百行一年几千行量不大但时间久了查询会慢。可以加一个inspect_date字段每天跑之前删掉 30 天前的数据或者按inspect_time建索引。第三把模型汇总接进来。巡检结果表跑完后用 Python 读结果调 TaoToken 的接口让模型生成一段摘要比如“今天有 3 个库表数量比昨天多 10% 以上2 个库巡检失败失败原因是权限不足”。这段摘要可以发到企业微信或者邮件。模型调用就用第 2 节配好的三件套Base URL 是 https://taotoken.net/api Key 从环境变量读。第四跨实例场景用配置表驱动。建一张dbo.InstanceList字段instance_name、conn_str、enabled。外层循环读这张表对每个启用的实例执行游标脚本。这样加实例只改表不改代码。注意conn_str里的密码要加密存储或者用 Windows 集成认证别明文写在表里。第五监控游标脚本本身的执行时间。如果某个库特别大sys.partitions查询也会慢。可以在结果表里加duration_ms字段记录每个库的巡检耗时超过阈值的库单独关注。我实测下来二十多个库的巡检大部分库在 100ms 内完成个别大库会到 1-2 秒整体可控。最后说一个实际技巧游标遍历所有数据库时如果库数量超过 50 个建议分批跑比如按首字母分两批避免一次性占用太多连接和锁。虽然sys.databases查询本身很轻但动态 SQL 跨库查询会短暂持有锁分批能降低对业务的影响。整套流程走下来你得到的是一套统一 Key 配置、一个可复制的游标脚本模板、一张全库巡检结果表、一套排障对照。下次再加库或者加实例改配置就行不用重写脚本。巡检结果汇总和模型对话可以在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 验证长期自动化可以看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。脚本先跑通单库再跑全量最后接定时任务这个顺序别跳。