简介:《archery使用手册》以单份 doc 文档形式呈现,面向数据库开发人员、DBA 及运维工程师,聚焦 SQL 审核查询平台 Archery 的入门与日常实践。内容围绕 SQL 审核与优化、实例管理、工具插件三条主线展开,涵盖 SQL 语法解析与自定义规则审核、高危语句驳回、定时或立即执行、慢 SQL 与执行计划分析,以及会话、事务、锁信息和账号参数管理等运维场景,并介绍 PTArchiver、Binlog2SQL、SchemaSync 等插件的用途与操作思路。压缩包共 1 个文件,为 1.2MB 的 doc 文档,便于在本地阅读、检索和打印。目前已有 1479 人学习,说明该手册在 Archery 入门与查阅场景中具有一定参考价值。读者可借此快速建立平台功能全貌,理解从 SQL 提交、检测、审核到执行与回滚的完整流程,并掌握实例巡检、阻塞排查和结构同步的常见做法,适合作为日常运维查阅和团队内部培训的辅助材料。
1. 从一个「SQL 检测不通过」说起
archery 这套 SQL 审核查询平台,多数人第一次上手卡住的地方不是部署,而是把一条在自己机器上跑得好好的建表语句贴进去,点「SQL 检测」,弹回来一片红字:没有主键、表没有注释、字段没有注释,运气差一点的直接判高危自动驳回。开发觉得莫名其妙,DBA 觉得理所当然,工单在两边来回踢,一条加字段的变更能耗掉半天。
这类问题最折腾人的地方在于它不是 bug,是审核规则在替你兜底,只是没人把「报错文案」和「命中了哪条规则」的对应关系讲清楚。下面的顺序是:看到什么报错、规则为什么拦、语句怎么改、执行阶段还会在哪儿翻车、最后怎么用一套固定动作快速定位。适合刚接手 archery 的研发、测试,以及要替团队配规则和实例的运维同学。
2. SQL 检测报 error:五类拦截的根因和改法
2.1 为什么「本地能跑」的语句过不了检测
archery 的检测不是把 SQL 丢到目标库上跑一遍再回滚,而是走内置的 SQL 解析器做语法分析,再拿解析结果去套后端配置好的审核规则集。这一步决定了几件事:检测阶段不碰真实数据,所以危险语句能在执行前被拦下来;解析器的语法覆盖范围和目标库版本不一定完全对齐,新版本特性在旧解析器上可能直接报语法错误;规则集是按实例或资源组绑定的,同一条语句在测试实例上过、在生产实例上不过,八成是两边绑了不同的规则模板。
把这条件链路记住就行:提交工单 → 选目标实例 → 解析 SQL → 套规则 → 返回 error 或 warning → 有 error 就卡住提交按钮。后面所有报错,基本都能归到这条链路的某一步。
还有一点容易忽略:warning 不拦提交,但会跟着工单一路飘到审核人那里。很多人看到「检测通过」就直接提交,审核人被一堆 warning 劝退打回,来回又是半天。提交前把 warning 也扫一眼,比事后解释省事。
2.2 建表类 error:主键、表注释、列注释三件套
最典型的一组报错文案是Must have a primary key、Table 'xxx' must have comment、Column 'xxx' needs comment,有的规则集还会加上字符集和存储引擎的限制。触发条件很明确:建表语句里缺主键、缺表级COMMENT、某个业务列没写COMMENT。下面这条就是标准的被驳回写法:
-- 会被规则拦下的建表语句:无主键、无表注释、列无注释、字符集未显式指定 CREATE TABLE t_order ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL, created_at DATETIME NOT NULL );改成能过检测的版本,改动点和规则一一对应:
CREATE TABLE t_order ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键 ID', user_id BIGINT NOT NULL COMMENT '下单用户 ID', amount DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '订单金额,单位元', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';逐项说明:PRIMARY KEY (id)对应主键规则;COMMENT='订单主表'对应表注释规则;每个列的COMMENT '...'对应列注释规则;ENGINE=InnoDB和DEFAULT CHARSET=utf8mb4是大多数团队规则集里强制的两项,缺了会报引擎或字符集不合规。DEFAULT 0.00和DEFAULT CURRENT_TIMESTAMP不是审核必需的,但NOT NULL列不给默认值,后续插入漏字段时会直接报错,顺手补上能少一次线上事故。
补一句实操经验:给存量表补注释是另一条独立工单,ALTER TABLE t_order COMMENT='订单主表'和逐列的MODIFY COLUMN ... COMMENT '...'都走 DDL 审核,别指望一条语句搞定所有列。改之前先SHOW CREATE TABLE把原结构抄下来,MODIFY COLUMN必须把原字段的类型、长度、默认值、NOT NULL全部重写一遍,漏一个属性就可能把字段改窄。
2.3 DML 类 error:没有 WHERE 的 UPDATE/DELETE 直接被驳回
第二类高频驳回是「DML 语句不带 WHERE 条件」,报错文案一般长这样:Update/Delete without where is forbidden。这类语句在审核环节就被判成 error,根本到不了审核人手里,因为规则里写死了:无过滤条件的全表更新和删除属于高危操作。
实际业务里真要做全表刷新怎么办?常见做法是加一个永真的主键范围条件,把「全表」变成「显式的全表」,同时用 LIMIT 控制单批行数:
-- 分批更新:主键范围 + LIMIT,既绕开无 where 拦截,也避免一次大事务 UPDATE t_order SET status = 2 WHERE status = 1 AND id > 0 AND id <= 100000 LIMIT 500;参数说明:status = 1是业务过滤条件,真正决定改动范围;id > 0 AND id <= 100000是主键区间,让优化器走主键范围扫描而不是全表扫;LIMIT 500限制单次影响行数,配合外层循环反复执行,直到影响行数为 0 为止。这里的顺序不能反:先按业务条件筛,再按主键区间切,最后 LIMIT 收口,这样每次执行都是可控的小事务,主从延迟也压得住。
还有一个版本要留意:不是所有规则模板都放行带 LIMIT 的 UPDATE,有些团队配置的是「UPDATE 必须命中索引」,那就得先用EXPLAIN确认type不是ALL,把WHERE落到有索引的列上再提交,否则检测照样报 error。
2.4 解析异常与检测超时:先把工单拆小
第三种报错不是规则拦的,是解析器自己出错:点「SQL 检测」转圈很久,最后返回超时,或者报一个语义不明的语法错误,而这条 SQL 单独拿到客户端里执行毫无问题。触发条件通常有三个:单个工单里塞了几千行 SQL、单条语句长到几万字符(大多是批量 INSERT 的 VALUES 拼接)、SQL 文件里混着存储过程的DELIMITER改写和大量注释。
处理思路是拆。按表拆、按功能拆、按发布批次拆,一个工单控制在人能一眼看完的规模。手上的 SQL 文件如果本来就是一整坨,先粗切一刀再人工过一遍:
# 按分号粗切 SQL 文件,注释里的分号会误伤,切完必须人工核对 awk 'BEGIN{RS=";"} {gsub(/^\n+/,""); if (length($0) > 0) print $0 ";"}' full_change.sql > split.sql # 统计切出来多少条,条数过多说明这个工单本身就不该整体提交 grep -c ';' split.sqlawk的RS=";"把记录分隔符改成;,gsub(/^\n+/,"")去掉每段开头的空行,末尾再补回分号。grep -c ';'只是数条数,用来判断规模是否离谱。这套切法对--行注释和/* */块注释里的分号没有免疫力,切完一定要打开文件逐段确认,别直接拿去提交。
存储过程、触发器、事件这类包含BEGIN ... END的对象,解析器处理起来本来就吃力,我的习惯是单独走一个 DDL 工单,或者干脆不走平台执行、只走审核记录。工单系统里混进一个几百行的存储过程,后面所有工单的检测都会跟着变慢。
2.5 报错文案对照表
把常见文案和执行动作对起来,比每次现查快得多:
| 报错或现象 | 命中规则/环节 | 处理动作 |
|---|---|---|
Must have a primary key | 建表规范 | 补PRIMARY KEY,自增列优先 |
Table 'xxx' must have comment | 表注释规范 | 建表语句末尾补COMMENT='...' |
Column 'xxx' needs comment | 列注释规范 | 每个业务列补COMMENT '...' |
Update/Delete without where | 高危 DML | 补业务条件 + 主键区间 +LIMIT |
| 语法错误但本地能执行 | 解析器版本差异 | 简化语法,避开新版本特性 |
| 检测长时间无响应 | 工单规模过大 | 按表或按功能拆分后再提交 |
| 只在生产实例报 error | 规则模板绑定不同 | 对比两个实例绑定的规则集 |
表格里最后一行值得单独说:测试环境过、生产环境不过,先别改 SQL,去实例管理里看两个实例绑的是不是同一个规则模板。模板不同的话,改 SQL 是治标,对齐模板才是治本。
3. 审核通过却执行失败:工单执行与回滚的四个现场
3.1 「立即执行」报连接失败:先分清审核库和目标库
现象很典型:工单一路审到最后一关,点「立即执行」,弹回来Can't connect to MySQL server或者Access denied for user。很多人第一反应是「刚才审核都过了怎么会连不上」,问题就出在这里——检测和审核阶段用的是解析器,压根没连目标库,真正建立连接是在执行那一刻。
根因基本落在三处:实例管理里配置的地址是给 archery 后端用的,不是你本机能通的地址;执行账号和审核账号是同一套配置,权限没覆盖目标库;实例地址填的是从库或者只读实例,写操作直接被拒。
定位时先在 archery 后端所在的机器上验一遍,注意是后端机器,不是你自己的笔记本:
# 用「实例管理」里配置的那套地址和账号,在后端机器上验证连通性与权限 mysql -h 10.0.0.12 -P 3306 -u archery_rw -p -e "SELECT 1;" 2>&1 # 确认账号对目标库有 DDL/DML 权限,看清楚授权范围到库还是到表 mysql -h 10.0.0.12 -P 3306 -u archery_rw -p -e "SHOW GRANTS FOR CURRENT_USER();"-h必须填实例里配的那个地址,容器化部署时尤其注意容器内的 DNS 和宿主机不是一套,配置里写127.0.0.1在容器里指向的是容器自己。SHOW GRANTS FOR CURRENT_USER()看的是GRANT ... ON db.*里的库名有没有覆盖目标库,只有ON *.*或者明确写到baidd.*才算数。如果返回的是Access denied而不是连接超时,别在网络上耗时间,直接查账号。
3.2 定时执行到点没跑:时间、队列和工单状态
第二种现场更隐蔽:选了「定时执行」,时间过了,工单状态还挂在「待执行」,页面上什么提示都没有。这类问题没有报错可看,只能顺着链路一层层排。
常见原因有三个:定时调度依赖后台的调度进程,进程没起来或者被重启掉了,任务就躺在库里没人捞;服务器时区和浏览器时区不一致,页面显示的是本地时间,后端按 UTC 存的,你以为定在 10 点,实际排在 18 点;前一个长任务把执行队列堵住了,后面排队的全部顺延。
# 1) 对比容器内时间与数据库时间,两者不一致先修时区 date mysql -h 127.0.0.1 -u archery -p -e "SELECT NOW(), @@global.time_zone, @@session.time_zone;" # 2) 捞最近半小时的后端日志,看调度和任务执行记录 docker logs --since 30m archery 2>&1 | grep -iE "schedul|celery|execute"date要在后端容器里执行,不是宿主机,容器默认 UTC 时区是这类问题最常见的来源。--since 30m只取最近半小时,避免日志刷屏,grep -iE一次匹配调度、队列、执行三类关键词。日志里能看到任务是否被调度、有没有异常堆栈,比盯着页面刷新有效得多。查到是队列堵塞的话,去实例管理里看那条长任务的会话还在不在,必要时终止,后面排队的工单会立刻恢复执行。
3.3 回滚 SQL 是空的或者回滚不生效
arbitrary 一点说,回滚 SQL 是这套平台的后悔药,但这颗药有时候是空的。现象是:工单执行完,点「查看回滚 SQL」,要么什么都没有,要么只生成了一部分。
原因分两类。DDL 的回滚语句是按表结构变更推导出来的,ADD COLUMN对应DROP COLUMN,MODIFY COLUMN对应改回原定义,前提是执行前后 archery 能读到正确的表结构;如果期间有人绕过平台手工改过同一张表,推导出来的回滚语句就是错的,照着执行会再捅一刀。DML 的回滚依赖执行前把受影响行捞出来存好,遇到带 JOIN、带子查询、带函数的 UPDATE,捞原始行的语句可能取不到完整数据,回滚 SQL 自然残缺。
养成一个习惯:DDL 变更前手工留底,回滚时以这份留底为准,不以平台生成的为准。
# DDL 变更前留底,文件名带日期,回滚时按这份结构人工比对 mysql -h 10.0.0.12 -P 3306 -u archery_rw -p -N \ -e "SHOW CREATE TABLE baidd.t_order\G" > t_order_before_$(date +%F).sql # 变更后再存一份,两份 diff 一下就知道到底改了什么 diff t_order_before_2025-01-01.sql t_order_after_2025-01-01.sql-N去掉结果集的列名,\G让SHOW CREATE TABLE竖排输出,可读性好很多。文件名里带日期,是因为同一张表一个月可能改好几次,靠before/after这种命名迟早分不清。两份文件diff出来的差异,比平台生成的回滚语句更可信。
注意:Binlog2SQL 这类基于 binlog 的回滚方案,只对 INSERT、UPDATE、DELETE 有效,DDL 操作的回滚信息不在 binlog 里,别指望它救回一张被 DROP 的表。
3.4 钉钉通知收不到,工单卡在「待审核」
最后一种现场不报错,只是「没人知道有我这张工单」。审核人没收到通知,工单就静静躺在待办列表里,开发等急了来问,审核人说没看见。
原因通常有四个:机器人 webhook 地址配错或者已经被替换;机器人安全设置选了自定义关键词,但消息模板里不含这个关键词;当前审核人没有被分配对应角色,待办压根没生成;消息发送频率触发了钉钉侧限流。
# 自测钉钉机器人是否可用,content 里必须包含机器人设置的关键词 curl -s -X POST 'https://oapi.dingtalk.com/robot/send?access_token=<token>' \ -H 'Content-Type: application/json' \ -d '{"msgtype":"text","text":{"content":"archery 通知自测"}}'返回errcode: 0说明 webhook 和关键词都对,问题在 archery 侧的角色配置;返回310000基本就是关键词不匹配或者签名校验没过,对着机器人设置页改content或者补签名字段。-s关掉进度条,-X POST显式声明方法,-d后面必须是合法 JSON,双引号别写成单引号。
4. 实例管理排障:磁盘撑爆、会话杀不掉、锁等半天
4.1 磁盘告警先清 binlog:选中哪个文件是有讲究的
现象是监控告警磁盘使用率冲到 90% 以上,登录机器一看,/var/lib/mysql下面一堆mysql-bin.0000xx。实例列表里提供了清理 binlog 的入口,选中某个文件,它之前的文件会被一起删掉——注意,是「之前的所有」,不是「选中的这一个」。这个交互设计踩过坑的人都懂,选错一个文件可能把还没被从库消费的 binlog 全删了。
动手之前先在目标实例上确认当前位点:
-- 确认当前正在写的 binlog 文件,这个文件无论如何不能删 SHOW MASTER STATUS; -- 有从库的话,确认从库已经消费到哪个文件 SHOW SLAVE STATUS\GSHOW MASTER STATUS返回的File是当前正在写的文件,在 archery 里选中它或者它之后的文件都是无效操作,界面通常也不允许。SHOW SLAVE STATUS的Relay_Master_Log_File表示从库已经执行到主库的哪个 binlog,比它更旧的文件才安全。没有从库但有 CDC 或离线同步任务在消费 binlog 的,先去确认那些任务的位点,再回来点清理。
清理之后磁盘空间不会立刻降下来,df还是老数字,ls看文件确实没了,这是文件句柄没释放,等一会儿或者重启 MySQL 才会还回去。别以为没删掉又点一次。
4.2 会话管理看不到卡住的 SQL:command 过滤和完整 INFO
会话管理页默认按Query类型过滤,这个默认值坑过不少人:业务反馈某条 SQL 卡住了,进页面翻半天找不到。原因是那条连接当时的状态可能是Sleep、Binlog Dump、Daemon,被过滤条件挡在外面了。
操作上就两步:右上角把 command 类型从默认的Query切到All,再把「完整 INFO」勾上。不勾的话 INFO 列是截断的,一条长 SQL 只能看到前半段,根本判断不出是哪个业务在跑。
命令行里等价的查法是:
-- 列出所有非 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;time是当前状态已经持续的秒数,几百秒以上的基本可以断定在卡;state能看出在等锁、在排序还是在写临时表;LEFT(info, 200)只取 SQL 前 200 个字符,避免一条大 SQL 把终端刷屏。command <> 'Sleep'过滤掉空闲连接,线上 Sleep 连接往往占大多数,不滤掉根本看不过来。
终止会话之前多看一眼:这条 SQL 如果已经跑了几十分钟、改了几十万行,KILL之后 MySQL 要回滚这些改动,回滚时间可能比执行时间还长,期间锁还继续占着。宁可先确认业务能不能接受,再决定杀不杀。
4.3 事务和锁串起来看:从 trx_mysql_thread_id 找到阻塞源头
业务侧的表现是「某个更新一直不返回」,页面上没有报错,就是转圈。这类问题要在事务信息和锁信息两个页面之间来回串。事务信息里能看到一个跑了几百秒还没提交的事务,它有个trx_mysql_thread_id;锁信息里能看到谁在等谁;拿着等待方的线程 id 去进程状态页按THREAD ID搜,就能找到阻塞方,然后终止。
本地复现很简单,开两个连接就行:
-- 会话 A:开事务但不提交,持有 id=2 这行的行锁 BEGIN; UPDATE baidd.t2 SET ename = 'baidd' WHERE id = 2; -- 会话 B:更新同一行,会被阻塞,直到 A 提交或回滚 UPDATE baidd.t2 SET ename = 'dan' WHERE id = 2;此时在 archery 的锁信息页里,能看到等待事务对应的 SQL,也就是会话 B 那条被阻塞的语句,旁边是阻塞方的线程 id。命令行里的等价查询分版本:
-- MySQL 5.7:锁等待关系直接在 innodb_lock_waits 里 SELECT waiting_trx_id, waiting_thread, blocking_trx_id, blocking_thread, blocking_query FROM information_schema.innodb_lock_waits;-- MySQL 8.0:走 performance_schema,再关联 innodb_trx 拿线程 id 和 SQL SELECT r.trx_mysql_thread_id AS waiting_thread, r.trx_query AS waiting_sql, b.trx_mysql_thread_id AS blocking_thread, b.trx_query AS blocking_sql FROM performance_schema.data_lock_waits w JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id;8.0 把锁等待信息从information_schema挪到了performance_schema.data_lock_waits,直接照抄 5.7 的 SQL 会报表不存在,这是升级后最常见的一条「查询突然不能用了」。两个版本里trx_mysql_thread_id都对应进程状态页里的THREAD ID,拿它去搜索,然后终止会话,阻塞立刻解除。
4.4 账号管理:授完权还是 Access denied
在图形界面里把账号建好、权限勾上,应用连过来还是Access denied。MySQL 的账号是user@host的组合,不是单纯一个用户名。界面里建出来的通常是app_rw@%,但如果库里早就存在一条app_rw@10.0.0.%的旧记录,MySQL 会优先匹配 host 更精确的那一条,你新授的权限根本没生效。
-- 看这个用户名下到底存在几条 host 记录,精确度高的会盖过 % SELECT user, host FROM mysql.user WHERE user = 'app_rw'; -- 分别看每条记录的权限,不要只看 % 那条 SHOW GRANTS FOR 'app_rw'@'%'; SHOW GRANTS FOR 'app_rw'@'10.0.0.%';mysql.user里同一个user出现多行是正常的,问题在于你只改了其中一行。查到有冗余记录,要么把旧记录的权限补齐,要么删掉旧记录只留一条,别两条都留着给自己埋雷。另外修改密码要通过界面或者ALTER USER走,直接UPDATE mysql.user改表之后不刷权限表,改完也不生效。
5. 常见问题避坑:三个工具插件的高频翻车现场
5.1 现象:PT-Archiver 归档任务提交了,数据一点没动
现象:归档工单显示执行成功,去源表一查,三个月前的数据一条没少,历史表里也没有。
原因基本是四种:归档条件写错了,WHERE里的时间字段和实际业务字段对不上,条件筛出来就是空集;目标归档表不存在或者结构和源表不一致,工具写了日志但没落数据;账号缺DELETE权限,读取正常、删除失败;任务被审核规则当成高危 DML 拦在了提交环节,根本没执行。
解决方式是先在命令行用同一套参数手工跑一次,加--dry-run看清楚它要做什么:
# 按 archery 里配置的同一套参数手工试跑,--dry-run 只打印不执行 pt-archiver --source h=10.0.0.12,P=3306,u=arch,p=***,D=baidd,t=t_flow \ --where "created_at < DATE_SUB(NOW(), INTERVAL 3 MONTH)" \ --dest h=10.0.0.12,P=3306,u=arch,p=***,D=baidd_arch,t=t_flow \ --limit 1000 --txn-size 1000 --commit-each --dry-run--source和--dest里的h/P/u/p/D/t分别对应主机、端口、用户、密码、库、表,参数之间用逗号连接。--where决定捞哪些行,先用SELECT COUNT(*)加上同样的条件确认行数不为零再跑。--limit 1000是每批捞 1000 行,--txn-size 1000是每 1000 行提交一次事务,--commit-each保证每批独立提交,减少锁持有时间。--dry-run只打印不写入,是排「到底有没有在干活」最快的办法。确认没问题再去掉--dry-run正式跑。
5.2 现象:Binlog2SQL 解析出来的回滚 SQL 是空的或者直接报错
现象:指定了文件和位点,点「获取 SQL」,要么什么都没有,要么报解析异常。
原因集中在三处:实例的binlog_format是STATEMENT或MIXED,而这类工具只认ROW;binlog_row_image是MINIMAL,binlog 里只记了被修改的列,生成的回滚语句缺字段;给的起始位点落在事务中间,解析器对不上事务边界。另外 DDL 本来就无法回滚,别拿它去找DROP TABLE的那条记录。
-- 两项设置不满足,后面的解析全是白费 SHOW VARIABLES LIKE 'binlog_format'; SHOW VARIABLES LIKE 'binlog_row_image';确认是ROW和FULL之后,再用mysqlbinlog把区间导出来,确认这段位点里真的有内容:
# 把 ROW 格式的 binlog 解成可读 SQL,先确认区间内有没有东西 mysqlbinlog --base64-output=decode-rows -v \ --start-position=857 --stop-position=1183 \ /var/lib/mysql/master-bin.000009 > /tmp/range.sql wc -l /tmp/range.sql--base64-output=decode-rows把二进制行事件解成伪 SQL,-v输出详细内容。--start-position必须落在事务的起始位置,落在中间会解析失败,找位点的办法是在mysqlbinlog输出里搜BEGIN或者事务的Xid事件。wc -l只有几行的,说明这个区间根本就是空的,位点填错了。
5.3 现象:SchemaSync 比对结果是「无差异」,但两边表结构明显不一样
现象:源库和目标库一个有三个表、一个只有一个表,比对结果却说没有差异,或者生成的 DDL 完全不是想要的。
原因通常是源和目标的连接配置写成了同一个实例,工具自己跟自己比,当然没差异;账号只有目标库的读权限、看不到information_schema里的元数据;两边表的字符集或排序规则不同,生成的语句里混进一堆CONVERT TO CHARACTER SET,把真正的结构差异埋了。
# 先比表清单和字符集,能最快排除「连错库」和字符集差异干扰 mysql -h 10.0.0.12 -u archery_rw -p -N \ -e "SELECT table_name, table_collation FROM information_schema.tables WHERE table_schema='baidd' ORDER BY 1;" mysql -h 10.0.0.13 -u archery_rw -p -N \ -e "SELECT table_name, table_collation FROM information_schema.tables WHERE table_schema='baidd' ORDER BY 1;"两个结果一对比,表多的那边和表少的那边立刻分明,如果两边输出一模一样,先怀疑配置里的 host 是不是写重复了。table_collation不一致的话,同步语句会带上大量字符集转换,先统一字符集再比对,结果干净得多。这类工具只比表结构不比数据,行数差异不在它的职责范围内,别拿它当数据校验用。
5.4 现象:SQL 优化建议全是 OK,慢 SQL 还是慢
现象:把一条慢 SQL 丢进「SQL 优化」页,分析报告一路 OK,执行计划也显示走了索引,但线上还是慢。
原因是执行计划依赖统计信息,统计信息过期时优化器给出的计划本身就是错的;报告里看到走索引,不代表回表行数少;还有一种情况是慢根本不在扫描上,而是在等锁,执行计划再漂亮也没用。
-- 分析前先刷新统计信息,否则报告基于旧基数算出来的 ANALYZE TABLE baidd.emp; -- 对比优化前后:重点看 type、rows、filtered、Extra 四列 EXPLAIN SELECT ename, sal FROM baidd.emp WHERE ename = 'dan_1';ANALYZE TABLE会重新采样索引基数,数据量变化大的表定期跑一次。type是ALL说明全表扫,rows是预估扫描行数,filtered是过滤后剩余比例,Extra里出现Using filesort、Using temporary就要留意。把SELECT *改成具体字段能减少回表和网络传输,但如果WHERE列上压根没索引,改字段解决不了问题。慢日志里按Lock_time排一下,排除掉锁等待的因素,剩下的才是真正要优化的 SQL。
6. 快速定位:一套固定顺序,比瞎点页面快得多
前面几个场景拆开看各不相同,真到了线上告警那一刻,脑子是空的。我的做法是把顺序固定下来,从最外层往里走,每一步都有明确的判断依据,不跳步。
第一步看工单状态和审核明细。状态停在「待审核」是人的问题,停在「待执行」是调度的问题,显示「已执行」但业务没变化才是 SQL 或数据的问题。审核明细里能看到当时命中了哪些规则、有没有 warning 被忽略,这是第一现场。
第二步看执行日志。执行失败的错误码都在这里,Access denied往账号权限走,Can't connect往网络和实例配置走,超时往目标库负载走。
第三步进实例管理,按「事务 → 锁 → 会话」的顺序看。先看有没有长事务,再看锁等待关系里的阻塞方,最后到会话页按THREAD ID搜到具体连接。顺序反了容易误杀:先看会话看到一条慢 SQL 就 KILL,结果它是被锁住的受害者,真正的阻塞方还活着。
第四步回到数据本身。变更类的往前走 binlog 和回滚 SQL,结构类的往前走SHOW CREATE TABLE留底和 SchemaSync 比对。
| 症状 | 第一现场 | 关键字段 |
|---|---|---|
| SQL 检测报 error | 审核明细 | 命中的规则名、error/warning 级别 |
| 立即执行失败 | 执行日志 | 错误码、目标实例地址 |
| 定时到点没跑 | 后端调度日志 | 任务 id、调度时间、时区 |
| 会话找不到卡住的 SQL | 会话管理 | command 类型、THREAD ID |
| 阻塞排查 | 锁信息 | 等待线程 id、阻塞线程 id |
| 磁盘告警 | 实例列表 | SHOW MASTER STATUS当前文件 |
| 回滚 SQL 为空 | 工单回滚页 | 语句类型是 DDL 还是 DML |
用这张表的时候有个小技巧:先定位「症状」那一列,别急着看「关键字段」,因为很多症状的第一现场是重合的。比如「会话找不到卡住的 SQL」和「阻塞排查」,最后都会落到进程状态页,区别只在于你是从锁信息里拿到的线程 id,还是自己一条条翻出来的。拿到线程 id 再搜,比一个个试快十倍。
从那以后我每次提工单前都强制走一遍:先本地把 SQL 贴进检测框跑一次,把 error 和 warning 都清干净;DDL 先SHOW CREATE TABLE存一份带日期的留底;执行完立刻确认回滚 SQL 拿得到。这三步加起来不到两分钟,能省掉的返工时间远超想象。希望帮到你。
本文还有配套的精品资源,点击获取