简介:《archery使用手册》面向研发工程师、测试人员与数据库管理员,系统讲解这款SQL审核查询平台的落地用法,帮助团队把SQL上线、优化与实例运维纳入规范化流程。压缩包共1个doc文件,约1.2MB,以图文结合的方式组织内容,便于对照操作界面逐步查阅。手册先梳理SQL审核机制,包括语法解析、主键与注释等规范检查、高危语句自动驳回、定时或立即执行以及钉钉通知;随后进入实践环节,分别给出开发环境与测试环境的审核流程示例,说明提交检测、逐级审批、执行与回滚SQL的完整链路。SQL优化部分涵盖执行计划查看、优化建议获取与慢日志统计;实例管理部分涉及binlog清理、会话与进程查看、事务和锁信息排查、账号授权及动态参数配置;工具插件部分则演示PTArchiver数据归档、Binlog2SQL解析回滚与SchemaSync结构比对同步。已有1479人学习,适合需要搭建SQL审核规范或补全MySQL运维手段的读者参考。
1. Archery 不是万能审核器:先弄清它在流程里的位置
线上出过一次没有 WHERE 条件的 UPDATE,复盘时发现 SQL 是开发直接连生产库跑的,既没有审核记录,也没有回滚脚本——这类事故的根子往往不是技术,而是流程缺了一个闸门。Archery 这个 SQL 审核查询平台干的正是这件事:把 SQL 提交、规则检测、人工审批、执行和回滚语句生成串成一条工单流,同时把 MySQL 的会话、事务、锁、binlog、账号管理收进同一个 Web 界面。它不替你写 SQL,也不替代备份,适合需要把上线动作收口的中小研发团队和兼做运维的 DBA。把它当流程闸门用,而不是当性能优化工具用,后面的配置才不会跑偏。
2. 部署与实例接入:从容器启动到第一条工单能提交
2.1 先定三件事:元数据库、解析器、通知通道
Archery 自身是一个 Django 应用,工单、用户、规则、审批流都落在它自己的元数据库里,所以第一个要定的是这个库放哪。常见做法是单独给一个 MySQL 实例或库,字符集固定 utf8mb4,不要和业务库混用——工单表的数据量会随使用时间涨起来,混在一起以后清理和备份都难受。元数据库的版本建议和你要管理的实例保持同一大版本,避免排序规则和时区处理上出现细微差异。
第二个是 SQL 解析器。Archery 的审核结果来自对 SQL 的语法解析,实际落地时底层通常接 Inception 或 GoInception 这类解析服务,由它返回语法树、执行计划预览和规则命中结果。解析器要和被审核实例的网络打通,否则「SQL 检测」按钮点下去只会转圈。选型上,如果团队只维护 MySQL 5.7/8.0,用成熟的 Inception 就够了;如果还要覆盖其他兼容 MySQL 协议的数据库,优先确认解析器对目标版本语法的支持程度,别等到上线当天才发现某个窗口函数解析不了。
第三个是通知通道。工单审批靠人盯页面不现实,钉钉或邮件通知要提前配好,否则工单提交后卡在「待审核」没人知道,最后又退化成微信群里喊一声。这三件事定完再动手部署,比装完再回头补要省事得多。
2.2 起服务:容器编排与关键环境变量
部署方式上,我一般用容器编排,把 Archery、元数据库、缓存三个服务放在一个 compose 文件里,解析器按需单独起。下面这份片段只保留关键项,镜像标签用环境变量注入,不要图省事写 latest。
services: archery: image: hhyo/archery:${ARCHERY_TAG} # 标签固定到具体 release,避免重建时被动升级 environment: DATABASE_HOST: mysql # 元数据库地址,容器内用服务名 DATABASE_PORT: "3306" DATABASE_USER: archery DATABASE_PASSWORD: ${MYSQL_PASSWORD} DATABASE_NAME: archery SECRET_KEY: ${SECRET_KEY} # 必须改,且不要提交进仓库 DEBUG: "False" ALLOWED_HOSTS: "archery.internal.example" CSRF_TRUSTED_ORIGINS: "https://archery.internal.example" CACHE_HOST: redis mysql: image: mysql:8.0 command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: archery volumes: - ./mysql-data:/var/lib/mysql redis: image: redis:7这份配置解决的是「服务能起来」和「配置不写死」两件事。SECRET_KEY影响会话签名,多个副本必须一致;ALLOWED_HOSTS和CSRF_TRUSTED_ORIGINS填错时的典型表现是登录后立刻被踢回登录页,或者提交表单报 403,排查方向直接看这两个值。元数据库的字符集参数写在 command 里而不是靠客户端设置,是因为建库时的排序规则会固化到表上,事后改代价很大。
容器起来后还要做两步初始化:先跑数据库迁移,再建管理员账号。
# 迁移表结构,输出里出现 No migrations to apply 说明已是最新 docker compose exec archery python manage.py migrate # 交互式创建管理员,用户名密码按团队规范来 docker compose exec archery python manage.py createsuperuser迁移命令要在应用容器内执行,因为依赖的 Python 包和配置都在容器里;在宿主机上直接跑会连不上元数据库。管理员账号只用于初始化和权限分配,日常审核应该用普通账号走工单流程,这一点在后面配资源组时会体现出来。
2.3 实例接入的正确顺序:资源组 → 实例 → 库 → 权限
很多人第一次用会先点「实例管理」加实例,加完发现普通用户看不到,又回头补权限。正确的顺序是先在后台建资源组,把资源组理解成「一组人和一组实例的可见性边界」,然后把实例挂到资源组,再把用户加进资源组,最后才是库和表的授权。
实例接入表单里有几个字段值得单独说:
| 字段 | 填什么 | 容易踩的点 |
|---|---|---|
| 实例名称 | 环境+用途,如 prod-order-01 | 重名会导致工单归属混乱 |
| 连接地址 | 可解析的域名或内网地址 | 不要填负载均衡后面的只读地址 |
| 端口 | 实际监听端口 | 非 3306 时解析器也要同步改 |
| 用户/密码 | 审核与执行账号 | 权限过大等于绕过审核 |
| 资源组 | 对应的业务组 | 不选则只有管理员可见 |
实例侧要准备的账号按最小权限拆开。审核查询只需要读元数据和执行计划,执行工单才需要 DDL/DML 权限,两者混用一个账号,等于给了审核流程一个后门。
-- 审核查询账号:只读 + 看进程 + 读 binlog 位置 CREATE USER 'archery_ro'@'192.0.2.%' IDENTIFIED BY '强密码'; GRANT SELECT, SHOW VIEW, PROCESS, REPLICATION CLIENT ON *.* TO 'archery_ro'@'192.0.2.%'; -- 执行账号:按库授权,不给全局 CREATE USER 'archery_rw'@'192.0.2.%' IDENTIFIED BY '强密码'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP ON `biz_db`.* TO 'archery_rw'@'192.0.2.%';PROCESS用于会话和事务页面,没有这个权限,进程列表只能看到自己的连接;REPLICATION CLIENT用于读取 binlog 列表和位置,回滚语句和 binlog 清理都依赖它。执行账号按库授权而不是*.*,是为了让误操作的影响面可控——审核规则能拦住明显的错,但拦不住「账号本来就不该有权限」。
2.4 连通性自检:三条 SQL 先跑通
实例加完不要急着提工单,先用实例配置里的账号手工连一次,跑三条语句。它们分别对应后面三个高频问题:权限够不够、回滚能不能生成、表名大小写会不会对不上。
-- 1. 当前账号到底有哪些权限 SHOW GRANTS FOR CURRENT_USER(); -- 2. 决定回滚语句能否生成的关键参数 SELECT @@version, @@binlog_format, @@binlog_row_image, @@lower_case_table_names; -- 3. binlog 是否可读(8.0.22 之后用 SHOW BINARY LOG STATUS) SHOW MASTER STATUS;binlog_format必须是 ROW,binlog_row_image必须是 FULL,否则回滚语句要么生成不出来,要么只还原部分列。lower_case_table_names在 Linux 上通常是 0,如果开发同学在大小写不敏感的环境建过表,同步到这边就会出现「表不存在」的报错。这三条查完,实例接入才算真正完成,而不是页面上显示「连接成功」就结束。
3. SQL 审核工单:规则集怎么配、回滚语句怎么留
3.1 一条工单背后的五个状态
从提交到执行完,工单大致经过五个状态:草稿、检测中、待审核、待执行、已执行或执行失败。检测阶段由解析器给出结果,分 error 和 warning 两级——error 会直接阻断提交,warning 只提示不拦截。这个区分很重要,团队如果把所有规则都设成 error,最后的结果是大家绕着平台走,直接在客户端执行。
待审核状态对应审批链,最后一级审核通过后才出现「立即执行」和「定时执行」两个按钮。执行阶段失败不会自动回滚,工单会停在失败状态,需要人工判断是重试还是走回滚。理解这条状态机,后面配审批流和排查「工单卡住」才有参照系。
3.2 规则集:把团队规范翻译成开关
规则配置的入口在后台管理里,按环境分规则集是最实用的做法。开发环境放宽注释和主键要求,生产环境收紧,避免一套规则把测试环境的正常操作也拦死。下面这张表是我一般会先落地的几条:
| 规则项 | 建议级别 | 说明 |
|---|---|---|
| 建表必须有主键 | error | 无主键表在归档和回滚时都很难处理 |
| 表和列必须有注释 | warning | 开发环境设 warning,生产设 error |
| UPDATE/DELETE 必须带 WHERE | error | 高危语句直接驳回,不给「手滑」留机会 |
| 单条 UPDATE 影响行数上限 | error | 阈值按表规模定,常见 1000 到 5000 |
| 禁止 SELECT * | warning | 配合 SQL 优化页面的建议一起用 |
| 禁止 DROP TABLE | error | 确实要删表走单独流程 |
阈值不要拍脑袋。影响行数上限设得太低,正常的分批更新也会被拦;设得太高,等于没设。比较稳妥的做法是先跑一周只记录不拦截,看实际工单的分布,再定阈值。规则集改完要在工单页面重新点一次「SQL 检测」,已有工单不会自动重算。
3.3 从提交到执行:一次完整操作
提工单时选实例、库、工单类型,把 SQL 贴进去,先点「SQL 检测」。检测报 error 就先改 SQL,报 warning 确认无误再点「SQL 提交」。下面这条语句可以作为练手样例,它同时触发注释规则和主键规则:
-- 故意写得不符合规范的建表示例,用来验证规则是否生效 CREATE TABLE order_flow ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(64) NOT NULL, amount DECIMAL(12,2) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE=InnoDB;这条语句缺表注释和列注释,如果注释规则设成 error,检测会直接拦下;如果设成 warning,就可以继续提交,用来验证审批链是否通。审批人登录后在右上角通知里看到待办,点进去确认 SQL 内容,最后一级通过后选择立即或定时执行。定时执行适合放在业务低峰,但要注意应用服务器和数据库的时区是否一致,差八小时的结果就是「明明设的凌晨两点,下午两点才跑」。
3.4 回滚语句:哪些有、哪些没有
回滚语句不是平台凭空生成的,它的来源是 binlog。DML 操作在 ROW 格式下会记录每一行的前镜像和后镜像,解析这些事件就能反向拼出回滚 SQL;DDL 操作在 binlog 里只记录语句本身,没有行级前镜像,所以平台给不出真正意义上的结构回滚。
-- 确认回滚链路可用的两个参数,改完对已有连接不生效,需要重连 SET GLOBAL binlog_format = 'ROW'; SET GLOBAL binlog_row_image = 'FULL';binlog_row_image设成 MINIMAL 时只记录主键和被修改列,回滚语句会缺列,恢复出来的数据不完整,这个坑在数据修复场景里非常致命。改完参数要确认写入配置文件,SET GLOBAL重启即失效。对于 DDL,我的习惯是在工单附件里贴一份手工回滚脚本,审核人看得到,执行人也不会在出事时临时找备份。
4. 实例管理排查:会话、事务、锁、binlog 四条线怎么走
4.1 进程状态:先看全,再动手
会话页面默认只显示 query 类型的进程,排查阻塞时先把过滤条件改成 All,再勾上「完整 INFO」,否则长 SQL 会被截断,看到一半根本判断不了它在干什么。页面背后就是information_schema.processlist,手工查的时候按执行时长倒序排。
-- 排除 Sleep 连接,按已执行秒数倒序,长事务和慢查询会排在前面 SELECT id, user, host, db, command, time, state, LEFT(info, 200) AS sql_head FROM information_schema.processlist WHERE command <> 'Sleep' ORDER BY time DESC LIMIT 50;id就是终止会话时要用的线程号,页面上点「终止会话」等价于KILL <id>。要注意两点:一是 KILL 之前确认这个连接属于哪个应用,别把主从复制线程或者备份任务杀了;二是 KILL 只是发信号,事务回滚可能还要跑一段时间,大事务杀完立刻查进程可能还在,别以为没生效又点一次。
4.2 事务与锁:线程 id 怎么对上
锁等待排查的关键是把三张表串起来:事务表给线程号,锁等待表给阻塞关系,进程表给 SQL 文本。MySQL 8.0 里锁信息在 performance_schema 下,5.7 用的是 innodb_lock_waits,页面已经把这一步封装好了,但理解对应关系才能处理页面覆盖不到的情况。
-- 当前运行的事务,trx_mysql_thread_id 对应 processlist.id SELECT trx_id, trx_state, trx_started, TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS running_sec, trx_mysql_thread_id, LEFT(trx_query, 200) AS query_head FROM information_schema.innodb_trx ORDER BY trx_started; -- 8.0 的锁等待关系,blocking_thread_id 就是阻塞方 SELECT waiting_pid, blocking_pid, waiting_query, blocking_query FROM performance_schema.data_lock_waits;复现锁等待很简单:一个会话BEGIN后更新某行不提交,另一个会话更新同一行就会卡住。前者是阻塞方,后者是等待方,页面上的「等待事务的 SQL」显示的是被阻塞那条。要终止阻塞,拿blocking_pid去进程页面搜索,确认业务归属后再 KILL。直接杀等待方只能解一时,阻塞方的事务还在,下一批请求照样堵。
4.3 清理 binlog:先确认没有从库在等这个文件
磁盘告警时清 binlog 是最快的止血手段,但也是最容易顺手制造二次故障的操作。页面上选中某个文件执行清理,效果是删除该文件之前的所有 binlog,选中的文件本身保留。手工执行时对应的是下面这条:
-- 删除指定文件之前的 binlog,目标文件及其之后的保留 PURGE BINARY LOGS TO 'mysql-bin.000123'; -- 清理前先看从库读到哪了 SHOW REPLICA STATUS\G第二个命令要在从库上跑,重点看Relay_Master_Log_File和Seconds_Behind_Source。如果从库还在读mysql-bin.000120,你 purge 到 000123,从库立刻报 1236 错误然后断掉。延迟大的从库要等它追上,或者把目标文件设得更早。另外不要用PURGE BINARY LOGS BEFORE NOW()一把梭,正在写的文件删不掉,但边界很难控制,出问题时连恢复的余地都没有。
4.4 账号与参数:把变更留痕
账号管理页面能做增删用户、授权、改密码,适合把日常的权限申请收进流程。手工建账号时注意主机位不要写%,按网段授权,密码交给平台的加密存储而不是贴在工单里。
-- 按网段授权,只给业务库的增删改查 CREATE USER 'app_rw'@'192.0.2.%' IDENTIFIED BY '强密码'; GRANT SELECT, INSERT, UPDATE, DELETE ON `biz_db`.* TO 'app_rw'@'192.0.2.%'; FLUSH PRIVILEGES;FLUSH PRIVILEGES在直接用 GRANT 语句时其实不必要,只有直接改权限表才需要,但很多人习惯带上,问题不大。参数配置页面改的是动态参数并记录历史,要注意SET GLOBAL只影响新连接,已有连接还是旧值,改完要么重连要么安排重启,别改完就以为生效了。
5. 插件与排查:pt-archiver、binlog2sql、schemasync 的边界和五条踩坑记录
5.1 pt-archiver:归档不是删数据
流水表只保留最近三个月是常见需求,pt-archiver 的做法是分批把老数据搬到归档库再从源库删除,每批一个事务,对线上压力可控。它最容易出事的地方是把「归档」和「清理」混为一谈——第一次跑一定要用 dry-run,确认 where 条件命中的行数和预期一致。
# 先 dry-run,只打印不执行,确认行数和条件 pt-archiver \ --source h=192.0.2.10,P=3306,u=archiver,p=***,D=biz_db,t=order_flow \ --where "created_at < DATE_SUB(NOW(), INTERVAL 3 MONTH)" \ --dest h=192.0.2.20,D=archive_db,t=order_flow \ --limit 1000 --txn-size 1000 --commit-each \ --dry-run --why-quit # 确认后去掉 --dry-run,真正搬迁并删除源库数据 pt-archiver \ --source h=192.0.2.10,P=3306,u=archiver,p=***,D=biz_db,t=order_flow \ --where "created_at < DATE_SUB(NOW(), INTERVAL 3 MONTH)" \ --dest h=192.0.2.20,D=archive_db,t=order_flow \ --limit 1000 --txn-size 1000 --commit-each \ --purge --why-quit--limit是单批取数,--txn-size是单事务处理量,两个值设成一样可以保证每批一个事务,失败时回滚边界清晰。--purge表示搬完删除源库数据,如果不加就只拷贝不删;反过来只想删不想搬的场景要用--no-delete的反面逻辑,别把参数记混。--why-quit会打印退出原因,定时任务失败时靠它定位是条件跑完了还是连接断了。归档任务建议挂在低峰定时执行,并给归档库单独评估容量,别搬着搬着把归档库也写满了。
5.2 binlog2sql:能回滚 DML,回滚不了 DDL
误更新之后的第一反应往往是找平台生成回滚语句,平台背后的解析逻辑和 binlog2sql 是一回事:按 binlog 文件和位点解析出行事件,加-B参数输出反向 SQL。用之前必须确认binlog_format=ROW,否则解析出来的是原始语句,回滚无从谈起。
# 解析指定文件和位点区间,-B 生成回滚 SQL python binlog2sql.py \ -h192.0.2.10 -P3306 -uarchery_ro -p*** \ -d biz_db -t order_flow \ --start-file='mysql-bin.000009' \ --start-pos=857 --stop-pos=1183 \ -B--start-pos和--stop-pos是字节位点,不是行号,用SHOW BINLOG EVENTS或者页面上的位点信息来定位。区间给大了解析慢,给小了漏语句,比较稳的做法是先按时间范围粗筛,再逐步收窄位点。生成的 SQL 不要直接全量灌回去,先挑几条在预发验证,确认字段顺序和字符集没问题。DDL 回滚不了这一点前面已经说过,唯一的后悔药是执行前的备份或者能反向执行的 DDL 脚本。
5.3 schemasync:只比结构,不比数据
跨环境同步表结构时,手工对比容易漏索引和默认值。schemasync 读取源库和目标库的 schema,生成一份升级 SQL 和一份对应的回滚 SQL,适合放进发布流程里做结构差异检查。它只比对表结构,行数据、触发器、存储过程都不在范围内,指望它做数据同步会失望。
# 生成结构差异脚本,输出目录里会有 patch 和 revert 两个文件 schemasync --output-directory ./sync_out \ mysql://user:pass@192.0.2.20:3306/baidd \ mysql://user:pass@192.0.2.10:3306/baidd执行前先把生成的 patch 文件通读一遍,重点看有没有 DROP 语句——源库缺某个表时,工具可能生成删表操作,而目标库那张表可能还有数据。回滚文件同样要验证,结构回滚在列类型变更时不一定能还原原始数据,改类型前该备份还是得备份。
5.4 五条踩坑记录
现象:SQL 检测通过,执行时报 1054 未知列。原因多半是审核实例和执行实例不是同一个,或者审核连的是延迟较大的从库,读到的表结构落后于主库。解决方式是把审核实例指向主库或延迟可控的从库,执行前再跑一次检测,跨环境发布时先同步结构再提工单。
现象:工单执行成功,但回滚语句是空的。查binlog_format发现是 MIXED 或者binlog_row_image是 MINIMAL,也可能是实例配置里那个账号没有REPLICATION CLIENT权限。解决是改成 ROW + FULL,补权限后重新提交工单,历史工单不会自动补生成回滚语句,需要手工解析。
现象:定时执行的工单到点没动。常见原因有三个:后台任务进程没起来、任务队列堵住、应用服务器和数据库时区不一致。解决是先看后台任务日志有没有消费记录,再对一下两边的SELECT NOW(),时区不一致就统一成 UTC 或者在配置里显式指定。
现象:普通用户登录后看不到实例,也看不到工单。资源组、实例、用户三者没有串起来。解决是后台把实例关联到资源组,再把用户加进同一个资源组,缺一环页面上就是空白。管理员能看到全部,所以这个问题往往是普通用户先发现。
现象:主库清了 binlog,从库报 1236 然后复制中断。从库还在请求已经被删掉的文件。解决是 purge 前先在从库确认Relay_Master_Log_File,目标文件不能晚于从库正在读的位置;已经断掉的用CHANGE REPLICATION SOURCE TO指到现存的最早文件重建复制,再评估数据一致性。
6. 上线前验证:把规则分级和回滚演练做成固定动作
规则配完不等于生效,我习惯按环境分三套:开发环境只拦语法错误和高危语句,测试环境加上注释和主键检查,生产环境再把影响行数上限和禁止 DROP 打开。分级的好处是测试同学不会因为一条缺注释的建表语句被卡半小时,而生产的闸门始终是紧的。改完规则集挑三条典型 SQL 各跑一遍检测,确认 error 和 warning 的落点和预期一致,再通知团队。
回滚演练比规则更值得投入。每月从已执行的工单里挑一条 DML,把当时的回滚语句拿到预发环境执行,验证字段顺序、字符集、外键约束都没问题。DDL 工单则验证备份是否可恢复,至少确认备份文件存在且能解压。这个动作看起来费时间,但真正出事的时候,能省下的是几个小时的数据修复。
| 检查项 | 入口 | 通过标准 |
|---|---|---|
| 元数据库字符集 | SHOW CREATE DATABASE archery | utf8mb4 |
| 实例 binlog 参数 | SELECT @@binlog_format, @@binlog_row_image | ROW / FULL |
| 审核账号权限 | SHOW GRANTS FOR CURRENT_USER() | 含 PROCESS、REPLICATION CLIENT |
| 工单全流程 | 提交一条改注释的 DDL | 检测、审批、执行、回滚语句齐全 |
| 定时任务 | 设一条五分钟后的工单 | 到点执行且有日志 |
从那以后我每次接新实例,都强制先跑一遍 2.4 节那三条 SQL,再提一条只改注释的工单走完整流程,最后把回滚语句下载下来看一眼。这三步加起来不到十分钟,却能挡掉后面绝大部分「平台不好用」的抱怨。希望帮到你。
本文还有配套的精品资源,点击获取