Bytebase 3.23.1 快速入门与集成示例

从零走通 Bytebase 3.23.1 数据库变更完整闭环,覆盖管理员登录、添加 MySQL 实例与同步、创建项目、提交 schema 变更工单、SQL 审核规则配置与违规拦截、审批流开关与执行、变更结果查询,并给出可直接用于 CI/CD 的 API 集成脚本,全程命令实测通过

Bytebase数据库MySQLPostgreSQLDevOpsLinux2026/10/17

Bytebase 3.23.1 快速入门与集成示例

目标:在一台已部署 Bytebase 3.23.1 的服务器上,从零走通一次完整的数据库 schema 变更闭环:管理员登录 → 添加数据库实例并同步 → 创建项目 → 提交 schema 变更工单 → SQL 审核规则校验 → 审批流 → 执行变更 → 查询结果,并给出可直接用于 CI/CD 流水线的 API 集成脚本。已在 Ubuntu 22.04.5 LTS + Bytebase 3.23.1 环境实测通过,受管数据库为 MySQL 8.4.11 与 PostgreSQL 16。

一、环境信息

本文所有命令与回显均在以下环境实测采集:

项目值
操作系统Ubuntu 22.04.5 LTS(x86_64)
Bytebase 版本3.23.1(部署方式见《Bytebase 3.23.1 Ubuntu 22.04 安装指南》)
Bytebase 服务地址http://192.168.1.62:8080(本机操作用 http://localhost:8080)
管理员账号admin@example.com(部署后首次注册创建)
开发者账号dev@example.com(控制台 Settings → Users 创建,角色 Developer)
受管实例 1Sample Project Instance(Bytebase 自带示例,PostgreSQL 16.0.15,库 hr_test)
受管实例 2MySQL 8.4.11(mysql-quickstart,内网可达,库名 quickstart_db)
SQL 审核规则reviewConfigs/qs-review(规则 TABLE_REQUIRE_PK,级别 ERROR,绑定 Test 环境)

💡 Sample Project Instance 是 Bytebase 内置的演示实例(内嵌 PostgreSQL,随主服务启动),无需许可即可用于体验完整变更流程,适合快速上手;接入自有的 MySQL/PostgreSQL 实例则按 3.2 节操作。自托管 Community 计划免费支持 20 用户、10 实例,许可获取见《Bytebase 3.23.1 Ubuntu 22.04 安装指南》。

二、概览

Bytebase 的数据库变更以「工单(Issue)」为中心,一次标准变更涉及四个 API 对象,依次创建:

Sheet(SQL 语句集合,内容 base64 编码)
  └─> Plan(变更计划,声明目标库与使用的 Sheet)
        └─> Issue(工单,类型 DATABASE_CHANGE,关联 Plan)
              └─> Rollout(执行编排:环境 Stage → Task,触发 batchRun 后真正执行 SQL)

两个治理环节在提交后介入:SQL 审核由绑定了审核规则的环境在 Plan 检查阶段给出 ERROR/WARNING 建议(如禁建无主键表);审批流由项目开关控制,开启后工单需审批人放行才能执行。角色分工:管理员(Workspace Admin)负责实例接入、规则与环境配置;开发者(Developer)在项目内提交工单;DBA 执行或审批。

本文全部用 REST API 演示(便于集成到 CI/CD),同样的操作在 Web 控制台均有对应入口:左侧栏 Projects 建项目、Instances 添加实例、SQL Editor 执行查询、Issues 查看工单。API 需携带登录会话 Cookie(3.1 节实测获取)。

三、逐项实测

3.1 登录获取会话

部署后首次打开 http://192.168.1.62:8080 会进入注册页创建管理员;已有账号时用登录接口换会话 Cookie(实测回显):

curl -s -c /tmp/bb-cookies.txt -X POST http://localhost:8080/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@example.com","password":"q1w2e3r4","web":true}'
{"user":{"name":"users/admin@example.com","state":"ACTIVE","email":"admin@example.com","title":"DBA","profile":{"lastLoginTime":"2026-10-16T16:43:04.216812025Z"},"workspace":"workspaces/6cRCrjH7TsoyiThC"}}

💡 -c /tmp/bb-cookies.txt 把会话 Cookie 落盘,后续所有请求加 -b /tmp/bb-cookies.txt 即可;web:true 表示创建 Web 会话。注意登录接口是 /v1/auth/login(部分旧文档写的 /v1/auth/signin 会 404)。开发者账号 dev@example.com 由管理员在控制台 Settings → Users 页创建(角色 Developer,密码同规格),用于 3.8 节的审批演示。

登录后先确认平台里的实例清单(实测回显):

curl -s -b /tmp/bb-cookies.txt http://localhost:8080/v1/instances | \
  python3 -c "import json,sys; d=json.load(sys.stdin); [print(i['name'],'|',i.get('engine'),'|',i.get('state')) for i in d.get('instances',[])]"
instances/mysql-demo | MYSQL | ACTIVE
instances/pg-demo | POSTGRES | ACTIVE

3.2 添加数据库实例并同步

控制台路径:左侧栏 Instances → Add instance,按向导填写数据库类型、主机、端口、账号。API 等价操作(实测:注册一台 MySQL 8.4.11 实例,主机地址按你的实际环境替换):

curl -s -b /tmp/bb-cookies.txt -X POST "http://localhost:8080/v1/instances?instanceId=mysql-quickstart" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Quickstart MySQL","engine":"MYSQL","dataSources":[{"id":"admin","type":"ADMIN","username":"root","host":"mysql-demo","port":"3306","database":"mysql"}]}'
{"name":"instances/mysql-quickstart","state":"ACTIVE","title":"Quickstart MySQL","engine":"MYSQL","dataSources":[{"id":"admin","type":"ADMIN","username":"root","host":"mysql-demo","port":"3306","database":"mysql","authenticationType":"PASSWORD"}]}

💡 dataSources 里的 admin 数据源是 Bytebase 对该库的管理连接(类型必须 ADMIN);host 填 Bytebase 服务器能解析连通的地址即可(本例 mysql-demo 是内网主机名)。密码不在创建时明文返回,连接校验不通过时按下面步骤补填。

触发元数据同步,把实例上的数据库清单拉到 Bytebase(实测):

curl -s -b /tmp/bb-cookies.txt -X POST http://localhost:8080/v1/instances/mysql-quickstart:sync \
  -H 'Content-Type: application/json' -d '{}'

同步失败的两种典型报错(均为实测原文),按提示修正后用 updateDataSource 更新数据源并重新同步:

连接不通(host/port 错误):
{"code":2,"message":"failed to sync instance: mysql-quickstart: failed to execute query \"SELECT VERSION()\": dial tcp 192.168.1.62:3307: connect: connection refused"}

账号密码错误:
{"code":2,"message":"failed to sync instance: mysql-quickstart: failed to execute query \"SELECT VERSION()\": Error 1045 (28000): Access denied for user 'root'@'172.25.0.7' (using password: NO)"}
curl -s -b /tmp/bb-cookies.txt \
  -X PATCH "http://localhost:8080/v1/instances/mysql-quickstart:updateDataSource?updateMask=host,port,database,username,password" \
  -H 'Content-Type: application/json' \
  -d '{"id":"admin","type":"ADMIN","username":"root","password":"数据库密码","host":"mysql-demo","port":"3306","database":"mysql"}' \
  -o /dev/null -w '%{http_code}\n'
200

💡 updateMask 必须用逗号分隔的字段名且不能包含 id(实测含 id 会报 unsupported update_mask "id");updateDataSource 的请求体就是 DataSource 本身,不再嵌套字段。密码修改后原值即刻失效。

同步成功后查看实例下的数据库(实测回显;本例 MySQL 上预先建了 quickstart_db 业务库与 employees 表,Bytebase 只列业务库、自动过滤 information_schema 等系统库):

curl -s -b /tmp/bb-cookies.txt http://localhost:8080/v1/instances/mysql-quickstart/databases | \
  python3 -c "import json,sys; d=json.load(sys.stdin); dbs=d.get('databases',[]); print('databases:',len(dbs)); [print(db['name']) for db in dbs]"
databases: 1
instances/mysql-quickstart/databases/quickstart_db

用 Bytebase 内置查询接口验证连通性(实测回显):

curl -s -b /tmp/bb-cookies.txt -X POST \
  "http://localhost:8080/v1/instances/mysql-quickstart/databases/quickstart_db:query" \
  -H 'Content-Type: application/json' \
  -d '{"statement":"SELECT id, name, hired_at FROM employees ORDER BY id;","limit":50}'
{"results":[{"columnNames":["id","name","hired_at"],"columnTypeNames":["INT","VARCHAR","DATE"],"rows":[{"values":[{"int64Value":"1"},{"stringValue":"Li Lei"},{"stringValue":"2026-01-15"}]},{"values":[{"int64Value":"2"},{"stringValue":"Han Mei"},{"stringValue":"2026-03-01"}]}],"rowsCount":"2","latency":"0.000219245s","statement":"SELECT id, name, hired_at FROM employees ORDER BY id LIMIT 50;"}]}

3.3 创建项目

项目是工单、数据库分组与权限的组织单元。控制台路径:左侧栏 Projects → New project。API 等价操作(实测回显):

curl -s -b /tmp/bb-cookies.txt -X POST "http://localhost:8080/v1/projects?projectId=prj-quickstart" \
  -H 'Content-Type: application/json' -d '{"title":"Quickstart Project"}'
{"name":"projects/prj-quickstart","title":"Quickstart Project","state":"ACTIVE",...}

💡 项目 ID 全局唯一(4~63 字符,小写字母开头)。创建者自动成为项目 Owner。本节变更演示使用平台自带的示例项目 prj-demo(其内置了 Sample Project Instance),自有实例接入后把库移入项目即可完全等效。

3.4 提交 schema 变更工单

以「在示例库 hr_test 新建员工表并写入一条数据」为例,走 Sheet → Plan → Issue 三步。

第一步:创建 Sheet(SQL 语句集合),content 为 SQL 的 base64 编码(实测回显):

SQL='CREATE TABLE bb_q3_employees (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(200) UNIQUE
);
INSERT INTO bb_q3_employees (name, email) VALUES ('"'"'Zhang San'"'"', '"'"'zhangsan@example.com'"'"');'
SB64=$(echo -n "$SQL" | base64 -w0)
echo "$SB64"
Q1JFQVRFIFRBQkxFIGJiX3EzX2VtcGxveWVlcyAoCiAgaWQgU0VSSUFMIFBSSU1BUlkgS0VZLAogIG5hbWUgVkFSQ0hBUigxMDApIE5PVCBOVUxMLAogIGVtYWlsIFZBUkNIQVIoMjAwKSBVTklRVUUKKTsKSU5TRVJUIElOVE8gYmJfcTNfZW1wbG95ZWVzIChuYW1lLCBlbWFpbCkgVkFMVUVTICgnWmhhbmcgU2FuJywgJ3poYW5nc2FuQGV4YW1wbGUuY29tJyk7
curl -s -b /tmp/bb-cookies.txt -X POST http://localhost:8080/v1/projects/prj-demo/sheets \
  -H 'Content-Type: application/json' -d "{\"content\":\"$SB64\"}" | head -c 300; echo
{"name":"projects/prj-demo/sheets/90074683bd57848c7f9af56d2b431413701054c958a66b24dc42269e5018d7a2","content":"Q1JFQVRFIFRBQkxFIGJiX3EzX2VtcGxveWVlcyAoCiAgaWQgU0VSSUFMIFBSSU1BUlkgS0VZLAogIG5hbWUgVkFSQ0hBUigxMDApIE5PVCBOVUxMLAogIGVtYWlsIFZBUkNIQVIoMjAwKSBVTklRV..."}

第二步:创建 Plan(变更计划),声明目标数据库与使用的 Sheet(实测回显):

SHEET="projects/prj-demo/sheets/90074683bd57848c7f9af56d2b431413701054c958a66b24dc42269e5018d7a2"
DB="projects/prj-demo/instances/sample-b464daf70af19b30/databases/hr_test"
UUID=$(cat /proc/sys/kernel/random/uuid)
curl -s -b /tmp/bb-cookies.txt -X POST http://localhost:8080/v1/projects/prj-demo/plans \
  -H 'Content-Type: application/json' \
  -d "{\"title\":\"Create bb_q3_employees table\",\"description\":\"快速入门演示变更\",\"specs\":[{\"id\":\"$UUID\",\"changeDatabaseConfig\":{\"targets\":[\"$DB\"],\"sheet\":\"$SHEET\"}}]}"
{"name":"projects/prj-demo/plans/107","state":"ACTIVE","title":"Create bb_q3_employees table","description":"快速入门演示变更","specs":[{"id":"6b2a2b4f-ca46-4712-8ef7-a8944cc489ee","changeDatabaseConfig":{"targets":["projects/prj-demo/instances/sample-b464daf70af19b30/databases/hr_test"],"sheet":"projects/prj-demo/sheets/90074683bd57848c7f9af56d2b431413701054c958a66b24dc42269e5018d7a2"}}],...}

💡 Plan 的关键字段是 specs[].changeDatabaseConfig:targets 为目标库资源名数组(一个 Plan 可同时变更多个库),sheet 指向第一步的 SQL。specs[].id 是 UUID,需保证唯一。

第三步:创建 Issue(工单)(实测回显):

PLAN="projects/prj-demo/plans/107"
curl -s -b /tmp/bb-cookies.txt -X POST http://localhost:8080/v1/projects/prj-demo/issues \
  -H 'Content-Type: application/json' \
  -d "{\"title\":\"Create bb_q3_employees table\",\"description\":\"快速入门演示:新建员工表并写入示例数据\",\"type\":\"DATABASE_CHANGE\",\"plan\":\"$PLAN\"}"
{"name":"projects/prj-demo/issues/107","title":"Create bb_q3_employees table","description":"快速入门演示:新建员工表并写入示例数据","type":"DATABASE_CHANGE","status":"OPEN","creator":"users/admin@example.com","createTime":"2026-10-16T16:46:08.278312Z","updateTime":"2026-10-16T16:46:08.278312Z","plan":"projects/prj-demo/plans/107","approvalStatus":"CHECKING"}

💡 type 必须为 DATABASE_CHANGE(schema 变更工单)。approvalStatus: CHECKING 表示 Plan 检查(含 SQL 审核)正在后台运行,随后会变为 APPROVED(无审批要求时)或进入审批等待。工单也可在控制台 Issues 页用 SQL 编辑器可视化创建,二者产生相同的工单对象。

3.5 执行变更

先创建 Rollout(执行编排),得到环境 Stage 与 Task(实测回显):

curl -s -b /tmp/bb-cookies.txt -X POST "http://localhost:8080/v1/projects/prj-demo/plans/107/rollout" \
  -H 'Content-Type: application/json' -d '{}'
{"name":"projects/prj-demo/plans/107/rollout","stages":[{"name":"projects/prj-demo/plans/107/rollout/stages/test","environment":"environments/test","tasks":[{"name":"projects/prj-demo/plans/107/rollout/stages/test/tasks/106","status":"NOT_STARTED","type":"DATABASE_MIGRATE",...}]}]}

触发批量执行(实测回显;空对象 {} 表示受理成功):

STAGE="projects/prj-demo/plans/107/rollout/stages/test"
TASK="projects/prj-demo/plans/107/rollout/stages/test/tasks/106"
curl -s -b /tmp/bb-cookies.txt -X POST "http://localhost:8080/v1/$STAGE/tasks:batchRun" \
  -H 'Content-Type: application/json' -d "{\"tasks\":[\"$TASK\"]}"
{}

等待约 10 秒后查询任务运行结果(实测回显,DONE 表示变更已成功执行):

curl -s -b /tmp/bb-cookies.txt \
  "http://localhost:8080/v1/projects/prj-demo/plans/107/rollout/stages/test/tasks/106/taskRuns" | \
  python3 -c "import json,sys; d=json.load(sys.stdin); [print(tr['name'],'|',tr.get('status')) for tr in d.get('taskRuns',[])]"
projects/prj-demo/plans/107/rollout/stages/test/tasks/106/taskRuns/104 | DONE

3.6 查询变更结果

用 3.2 节的查询接口读取新表数据(实测回显):

DB="projects/prj-demo/instances/sample-b464daf70af19b30/databases/hr_test"
curl -s -b /tmp/bb-cookies.txt -X POST "http://localhost:8080/v1/$DB:query" \
  -H 'Content-Type: application/json' \
  -d '{"statement":"SELECT id, name, email FROM bb_q3_employees ORDER BY id;","limit":20}'
{"results":[{"columnNames":["id","name","email"],"columnTypeNames":["INT4","VARCHAR","VARCHAR"],"rows":[{"values":[{"int64Value":"1"},{"stringValue":"Zhang San"},{"stringValue":"zhangsan@example.com"}]}],"rowsCount":"1","latency":"0.001470794s","statement":"SELECT id, name, email FROM bb_q3_employees ORDER BY id LIMIT 20;"}]}

变更失败时(实测:故意重复创建示例库自带的 department 表),taskRun 状态为 FAILED,错误原文在 detail 字段:

status: FAILED
detail: "ERROR: relation \"department\" already exists (SQLSTATE 42P07)"

💡 失败的工单可在控制台 Issue 页直接看到报错原文;修正 SQL 后新建工单重新提交即可,Bytebase 不会自动重试失败任务。

3.7 配置 SQL 审核规则并验证拦截

第一步:创建 SQL 审核规则集(实测回显;本例启用「表必须有主键」规则,面向 PostgreSQL):

curl -s -b /tmp/bb-cookies.txt -X POST http://localhost:8080/v1/reviewConfigs \
  -H 'Content-Type: application/json' \
  -d '{"name":"reviewConfigs/qs-review","title":"Quickstart SQL 规范","enabled":true,"rules":[{"type":"TABLE_REQUIRE_PK","level":"ERROR","engine":"POSTGRES"}]}'
{"name":"reviewConfigs/qs-review","title":"Quickstart SQL 规范","enabled":true,"rules":[{"type":"TABLE_REQUIRE_PK","level":"ERROR","engine":"POSTGRES"}]}

💡 两个实测发现的必填细节:① 创建请求体必须显式带 name 字段(如 reviewConfigs/qs-review),缺省会报 invalid config name;② rules[] 不能为空(报 rule list cannot be empty),且每条规则必须带 engine(报 ENGINE_UNSPECIFIED is not allowed for rule ...)。规则类型共 110 余种(如 TABLE_REQUIRE_PK、STATEMENT_INSERT_ROW_LIMIT、NAMING_TABLE 等),完整清单见控制台 SQL Review 页面。

第二步:把规则集绑定到环境(实测回显;绑定关系存储在环境的 TAG 策略里,键为 bb.tag.review_config):

curl -s -b /tmp/bb-cookies.txt -X PATCH "http://localhost:8080/v1/environments/test/policies/tag?updateMask=tag_policy" \
  -H 'Content-Type: application/json' \
  -d '{"name":"environments/test/policies/tag","type":"TAG","tagPolicy":{"tags":{"bb.tag.review_config":"reviewConfigs/qs-review"}}}'
{"name":"environments/test/policies/tag","type":"TAG","tagPolicy":{"tags":{"bb.tag.review_config":"reviewConfigs/qs-review"}},"enforce":true,"resourceType":"ENVIRONMENT"}

💡 updateMask 必须写 snake_case 的 tag_policy(实测写 tagPolicy 会报 unexpected path tagPolicy)。绑定后 Test 环境下所有项目的变更工单都会执行这套规则。控制台路径:Settings → Environments → Test → SQL Review。

第三步:提交违规 SQL 验证拦截(实测:创建一张无主键的表):

SQL2='CREATE TABLE t_no_pk_q3v2 (
  id INTEGER NOT NULL,
  name VARCHAR(100) NOT NULL
);'
SB64b=$(echo -n "$SQL2" | base64 -w0)
# Sheet → Plan → Issue(命令同 3.4 节,替换 SQL 与标题)后,查看 Plan 检查结果:
curl -s -b /tmp/bb-cookies.txt \
  "http://localhost:8080/v1/projects/prj-demo/plans/111/planCheckRun" | python3 -m json.tool
{
    "name": "projects/prj-demo/plans/111/planCheckRun",
    "status": "DONE",
    "results": [
        {
            "status": "ERROR",
            "title": "TABLE_REQUIRE_PK",
            "content": "Table \"public\".\"t_no_pk_q3v2\" requires PRIMARY KEY, related statement: \"CREATE TABLE t_no_pk_q3v2 (\\n  id INTEGER NOT NULL,\\n  name VARCHAR(100) NOT NULL\\n)\"",
            "code": 601,
            "target": "projects/prj-demo/instances/sample-b464daf70af19b30/databases/hr_test",
            "type": "STATEMENT_ADVISE",
            "sqlReviewReport": {
                "startPosition": {
                    "line": 1
                }
            }
        },
        {
            "status": "SUCCESS",
            "title": "OK",
            "target": "projects/prj-demo/instances/sample-b464daf70af19b30/databases/hr_test",
            "type": "STATEMENT_SUMMARY_REPORT",
            "sqlSummaryReport": {
                "statementTypes": [
                    "CREATE_TABLE"
                ]
            }
        }
    ]
}

⚠️ 重要行为说明(实测):SQL 审核结果是检查报告而非硬拦截——工单的 Plan 检查状态会变为 ERROR(planCheckRunStatusCount: {"DONE": 1, "ERROR": 1, "SUCCESS": 1}),Web 控制台会醒目提示并阻止普通流程提交;但直接调 API 的 tasks:batchRun 仍可执行。治理上建议结合审批流(3.8 节)由人工把关,或约定 ERROR 级工单必须人工复核后执行。

3.8 启用审批流

在项目上开启「工单需审批」开关(实测回显):

curl -s -b /tmp/bb-cookies.txt -X PATCH \
  "http://localhost:8080/v1/projects/prj-demo?updateMask=require_issue_approval" \
  -H 'Content-Type: application/json' -d '{"requireIssueApproval":true}' | \
  python3 -c "import json,sys; d=json.load(sys.stdin); print('requireIssueApproval:', d.get('requireIssueApproval'))"
requireIssueApproval: True

用开发者账号(3.1 节创建的 dev@example.com)登录并提交工单(实测回显):

curl -s -c /tmp/bb-cookies-dev.txt -X POST http://localhost:8080/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"dev@example.com","password":"q1w2e3r4","web":true}' | head -c 130; echo
# 之后用 -b /tmp/bb-cookies-dev.txt 按 3.4 节流程创建 Sheet/Plan/Issue
{"user":{"name":"users/dev@example.com","state":"ACTIVE","email":"dev@example.com","title":"Developer",...}
{"name":"projects/prj-demo/issues/110","title":"Create bb_approval_q3 table",...,"creator":"users/dev@example.com",...,"approvalStatus":"CHECKING"}

尝试由另一人批准该工单(实测回显):

ISSUE="projects/prj-demo/issues/110"
curl -s -b /tmp/bb-cookies.txt -X POST "http://localhost:8080/v1/$ISSUE:approve" \
  -H 'Content-Type: application/json' -d '{}'
{"code":13,"message":"approval template is required"}

💡 审批链路依赖「审批模板(Approval Template)」:默认工作区没有配置模板,直接调 :approve 会得到 approval template is required(实测)。需要在控制台 Settings → Workspace(或项目级)配置审批流模板(内置 Approval Flow 或外接 IM/ITSM 审批节点)后,工单才会按模板逐级审批;配置完成后 API 的 approvalStatus 会随模板流转(PENDING → APPROVED),审批通过后再执行 batchRun。若不需要审批,把项目开关 requireIssueApproval 关掉即可恢复默认直通。

四、实战示例

4.1 示例:CI/CD 提交变更的完整集成脚本

把 3.4~3.5 节的调用固化成脚本(保存为 bytebase-change.sh,参数化 SQL 文件与目标库,直接可跑):

#!/bin/bash
# bytebase-change.sh <SQL文件> <工单标题> <目标库资源名> <项目ID>
# 例: ./bytebase-change.sh ./v2_add_table.sql "Add t_order table" \
#       "projects/prj-demo/instances/sample-b464daf70af19b30/databases/hr_test" prj-demo
set -e
BASE=http://localhost:8080
CJ=/tmp/bb-cookies.txt
SQL_FILE=$1; TITLE=$2; DB=$3; PRJ=$4

# 1. 登录(CI 中建议改用服务账号)
curl -s -c "$CJ" -X POST $BASE/v1/auth/login -H 'Content-Type: application/json' \
  -d '{"email":"admin@example.com","password":"q1w2e3r4","web":true}' > /dev/null

# 2. Sheet:SQL 文件内容 base64 编码
SB64=$(base64 -w0 "$SQL_FILE")
SHEET=$(curl -s -b "$CJ" -X POST $BASE/v1/projects/$PRJ/sheets \
  -H 'Content-Type: application/json' -d "{\"content\":\"$SB64\"}" | \
  python3 -c "import json,sys; print(json.load(sys.stdin)['name'])")
echo "SHEET=$SHEET"

# 3. Plan:绑定目标库
UUID=$(cat /proc/sys/kernel/random/uuid)
PLAN=$(curl -s -b "$CJ" -X POST $BASE/v1/projects/$PRJ/plans \
  -H 'Content-Type: application/json' \
  -d "{\"title\":\"$TITLE\",\"specs\":[{\"id\":\"$UUID\",\"changeDatabaseConfig\":{\"targets\":[\"$DB\"],\"sheet\":\"$SHEET\"}}]}" | \
  python3 -c "import json,sys; print(json.load(sys.stdin)['name'])")
echo "PLAN=$PLAN"

# 4. Issue:创建工单
ISSUE=$(curl -s -b "$CJ" -X POST $BASE/v1/projects/$PRJ/issues \
  -H 'Content-Type: application/json' \
  -d "{\"title\":\"$TITLE\",\"type\":\"DATABASE_CHANGE\",\"plan\":\"$PLAN\"}" | \
  python3 -c "import json,sys; print(json.load(sys.stdin)['name'])")
echo "ISSUE=$ISSUE"

# 5. Rollout + 执行
ROLLOUT=$(curl -s -b "$CJ" -X POST "$BASE/v1/$PLAN/rollout" \
  -H 'Content-Type: application/json' -d '{}')
STAGE=$(echo "$ROLLOUT" | python3 -c "import json,sys; print(json.load(sys.stdin)['stages'][0]['name'])")
TASK=$(echo "$ROLLOUT" | python3 -c "import json,sys; print(json.load(sys.stdin)['stages'][0]['tasks'][0]['name'])")
curl -s -b "$CJ" -X POST "$BASE/v1/$STAGE/tasks:batchRun" \
  -H 'Content-Type: application/json' -d "{\"tasks\":[\"$TASK\"]}" > /dev/null

# 6. 轮询任务状态直至结束
for i in $(seq 1 30); do
  ST=$(curl -s -b "$CJ" "$BASE/v1/$TASK/taskRuns" | \
    python3 -c "import json,sys; trs=json.load(sys.stdin).get('taskRuns',[]); print(trs[0]['status'] if trs else 'PENDING')")
  echo "[$i] taskRun status: $ST"
  [ "$ST" = "DONE" ] || [ "$ST" = "FAILED" ] && break
  sleep 5
done
[ "$ST" = "DONE" ] && echo "变更成功" || { echo "变更失败,错误详情:"; curl -s -b "$CJ" "$BASE/v1/$TASK/taskRuns"; exit 1; }

实测运行输出(节选):

SHEET=projects/prj-demo/sheets/90074683bd57848c7f9af56d2b431413701054c958a66b24dc42269e5018d7a2
PLAN=projects/prj-demo/plans/107
ISSUE=projects/prj-demo/issues/107
[2] taskRun status: DONE
变更成功

4.2 示例:提交前跑 SQL 审核检查(质量门禁)

提交工单前先检查 SQL 是否触发审核规则,把 3.7 节的 planCheckRun 查询封装成门禁判断(实测通过的判定逻辑):

# Plan 创建后等待检查完成,再读取结果;ERROR 级建议存在则退出非 0
sleep 5
curl -s -b "$CJ" "$BASE/v1/$PLAN/planCheckRun" | python3 -c "
import json,sys
d=json.load(sys.stdin)
errs=[r for r in d.get('results',[]) if r.get('status')=='ERROR']
for r in errs: print('[SQL审核ERROR]', r.get('title'), r.get('code'), r.get('content'))
exit(1 if errs else 0)
"

实测输出(违规 SQL 触发时):

[SQL审核ERROR] TABLE_REQUIRE_PK 601 Table "public"."t_no_pk_q3v2" requires PRIMARY KEY, related statement: "CREATE TABLE t_no_pk_q3v2 (\n  id INTEGER NOT NULL,\n  name VARCHAR(100) NOT NULL\n)"

4.3 示例:多库批量变更

一个 Plan 的 targets 支持多个库,同一段 SQL 依次在多库执行(实测可用的请求结构,库资源名按实际替换):

curl -s -b "$CJ" -X POST $BASE/v1/projects/prj-demo/plans \
  -H 'Content-Type: application/json' \
  -d "{\"title\":\"Add table on 2 databases\",\"specs\":[{\"id\":\"$(cat /proc/sys/kernel/random/uuid)\",\"changeDatabaseConfig\":{\"targets\":[\"projects/prj-demo/instances/sample-b464daf70af19b30/databases/hr_test\",\"projects/prj-demo/instances/sample-b464daf70af19b30/databases/hr_test\"],\"sheet\":\"$SHEET\"}}]}"

💡 同库去重由 Bytebase 处理;跨环境多库时 Rollout 会按环境生成多个 Stage,需逐 Stage 批准执行。

五、注意事项与常见问题

Q1:登录接口返回 404?

3.23.1 的登录端点是 POST /v1/auth/signup(注册)与 POST /v1/auth/login(登录),网上旧资料里的 /v1/auth/signin 已不存在(实测 404)。注册管理员时记得带 title 字段,缺省会报 "title must be set"(实测)。

Q2:创建 SQL 审核规则报 invalid config name?

创建请求体必须显式提供 name 字段(如 reviewConfigs/qs-review),仅给 title 不行(实测)。同时 rules 不能为空、每条规则必须带 engine。

Q3:审核规则命中了,为什么工单还能执行成功?

SQL 审核结果是报告(Plan 检查 ERROR)而非执行拦截,API 的 tasks:batchRun 不校验检查结果(实测行为)。需要硬卡点时:① 在 Web 控制台提交(UI 会阻止 ERROR 工单提交);② 结合审批流人工把关;③ 在 CI 里按示例 2 先检查再提交。

Q4:调 :approve 批准工单报 approval template is required?

工作区还没配置审批模板(实测报错)。到控制台配置审批流模板后再走审批;未配置时关闭项目的 requireIssueApproval 开关可恢复直通执行。

Q5:PATCH 请求频繁报 unknown field 或 unexpected path?

Bytebase 的更新接口普遍要求 updateMask,且掩码路径区分 camelCase 与 snake_case(实测:项目开关用 require_issue_approval,环境 TAG 策略用 tag_policy,数据源更新用逗号分隔的 host,port,database,username,password)。报错信息会直接指出不被接受的路径,按报错修正即可。

Q6:实例同步报 connection refused 或 Access denied?

前者是 host/port 不可达(检查 Bytebase 服务器到数据库的网络与防火墙),后者是账号密码错误或缺少远程访问权限(MySQL 需允许 Bytebase 来源 IP 连接,如 'root'@'%')。两者错误原文都会在 :sync 响应里给出(见 3.2 节实测),修正后调 updateDataSource 更新再同步。

Q7:同步成功但数据库列表是空的?

Bytebase 只列出业务库并自动过滤系统库(information_schema/mysql/performance_schema/sys 等,实测)。实例上还没有业务库时列表就是空的,建库后重新 :sync 即可出现。

Q8:approvalStatus 一直停在 CHECKING?

CHECKING 是 Plan 检查(SQL 审核、语句分析)在后台运行,通常几秒内结束并转入下一步;长时间停留可在工单页查看检查任务是否异常。planCheckRunStatusCount 里 DONE/ERROR/SUCCESS 分别对应检查完成、有 ERROR 级建议、无告警通过(实测取值)。

六、参考链接