楠风测开 · 测试工程师复习日记 Day17
把一次"接口慢"的反馈,整理成可复查、可交接的诊断材料
前言
朋友们好,我是楠风。
在工程协作里,性能问题常常以一句"订单列表偶尔很慢"开始。开发拿到SQL,测试拿到接口耗时,运维拿到数据库曲线,三份材料却不属于同一个窗口。讨论很热闹,结论仍然只能靠猜。
今天的目标,是让一次排查留下别人能继续验证的材料。我们承接Day16的nanfeng_mysql_lab.nf_order_bench十万行实验表,使用MySQL 8.4 LTS / InnoDB。下面给出可执行实验,当前未提供本机实测性能数据;所有操作限定在自己控制、没有其他业务连接的独立实验实例。
一、先设计证据记录,再决定用哪个工具
不要先决定"我要开慢日志",先写这次调查的对象:订单查询、用户42、已支付状态、十月时间范围、按创建时间和ID取前20条。
记录格式可以很简单。下面是待填的JSON示意,null表示尚未采集,不代表耗时为0:
json
{
"case": "nf17-order-list",
"mysql_version": null,
"instance": null,
"window_start": null,
"window_end": null,
"connection_id": null,
"session_long_query_time_s": null,
"table_rows": null,
"index_snapshot": null,
"pool_wait_ms": null,
"db_client_roundtrip_ms": null,
"application_after_sql_ms": null,
"ordered_result_ids": null,
"slow_log_sample": null,
"plan": null,
"lock_wait_snapshot": null
}
应用里的计时应使用同一请求的阶段边界:等待连接、执行并取得结果、后续处理。客户端数据库往返包含驱动和传输影响,不能直接等同于服务端语句时间。只要口径先写清楚,暂时缺一项也比把几个不同时间随意相减可靠。
请求ID留在应用日志里,用时间、实例和数据库连接信息帮助对应;不要默认一条慢日志一定自带业务请求ID。日志文件时区还要与应用记录对齐,可检查@@GLOBAL.log_timestamps,避免把相差数小时的记录拼成同一现场。
二、采集器首先要能说明自己改变了什么
本次不修改持久化配置,不改全局慢查询阈值,也不改日志文件路径。只在一个固定会话短时采集目标SQL,并恢复旧设置。全局日志开关和输出仍影响整个实例,所以必须使用独立环境。
在同一个保持连接的控制会话执行:
sql
USE nanfeng_mysql_lab;
SELECT VERSION(), CONNECTION_ID(), @@hostname;
SELECT COUNT(*) FROM nf_order_bench;
SHOW INDEX FROM nf_order_bench;
SET @nf_old_slow = @@GLOBAL.slow_query_log;
SET @nf_old_output = @@GLOBAL.log_output;
SET @nf_old_time = @@SESSION.long_query_time;
SET @nf_old_rows = @@SESSION.min_examined_row_limit;
SELECT @nf_old_slow, @nf_old_output, @nf_old_time, @nf_old_rows;
SELECT @@GLOBAL.datadir, @@GLOBAL.slow_query_log_file,
@@GLOBAL.general_log, @@GLOBAL.log_timestamps;
将显示的旧值单独保存,防止连接断开后丢失。初始索引可能只有主键,也可能已有idx_user_status_created(user_id,status,created_at);记录真实状态,不要默默假设所有读者环境一致。
全局系统变量的设置需要相应管理权限,例如SYSTEM_VARIABLES_ADMIN;文件读取还涉及服务器主机权限。权限不够就使用管理员提供的实验材料或进行已授权的计划分析,不在教程里扩大权限范围。系统变量权限
短时开启采集,并在这个会话运行固定查询:
sql
SET GLOBAL log_output = 'FILE';
SET GLOBAL slow_query_log = ON;
SET SESSION long_query_time = 0;
SET SESSION min_examined_row_limit = 0;
SELECT id, user_id, status, created_at
FROM nf_order_bench
WHERE user_id = 42 AND status = 'PAID'
AND created_at >= '2026-10-01'
AND created_at < '2026-11-01'
ORDER BY created_at, id
LIMIT 20;
阈值0用于验证采集链路,不定义业务上的慢。修改全局long_query_time不会替换现有会话的值;若用应用连接池复现,必须确认实际执行连接的会话设置,不能只看管理连接的显示值。系统变量作用域
执行少量目标查询、保存结果和窗口后,立即恢复:
sql
SET SESSION long_query_time = @nf_old_time;
SET SESSION min_examined_row_limit = @nf_old_rows;
SET GLOBAL slow_query_log = OFF;
SET GLOBAL log_output = @nf_old_output;
SET GLOBAL slow_query_log = @nf_old_slow;
SELECT @@GLOBAL.slow_query_log, @@GLOBAL.log_output,
@@SESSION.long_query_time,
@@SESSION.min_examined_row_limit;
log_output也控制通用查询日志的输出;恢复时应回到保存的原值,而非写死一个"推荐配置"。中间报错也要恢复已改变的项。断线后用保存的明确旧值恢复,不在新连接里继续引用已失效的@变量。
从服务器日志中截取这段实验窗口,保留原始片段。相对日志路径基于服务器数据目录;容器内的路径不等于客户端路径。不要清空日志来方便筛选。日志参数与记录规则
三、对SQL分组,但保留让问题发生的参数
日志分析可以分两层:先按SQL结构找到候选,再回看候选里的具体参数。第一层解决重复,第二层防止把数据倾斜抹平。
如果环境有mysqldumpslow,对导出的实验片段执行:
bash
mysqldumpslow -s at -t 10 /path/to/nf17-sample.log
mysqldumpslow -s c -t 10 /path/to/nf17-sample.log
分别从平均查询时间和次数两个方向筛选。路径要替换为实际文件;这个工具的字面量归一化适合汇总,不等于完整的业务聚类。mysqldumpslow用法
筛选后的卡片至少保留:查询类型、窗口内记录数、代表参数、耗时范围、扫描与返回行数,以及对业务的影响。慢日志有阈值筛选,所以这里的次数只代表被记录的次数。不要把它当总调用量,也不要拿这些截断样本计算全量P95。
优先级也不能只按"最慢一条"。一个偶尔执行的离线查询,和用户关键路径上的高频查询,处理目标可能不同。先对齐影响范围,再选择最小验证;这样才能解释为什么本轮先查这个对象。
四、把三类假设拆成三条验证路径
| 候选原因 | 需要的关键证据 | 什么情况会动摇这个判断 |
|---|---|---|
| 扫描或排序工作量过大 | 计划、实际行数、参数分布、索引 | 访问量很小,但等待关系持续存在 |
| 事务等待 | 等待期间的请求方、阻塞方、事务上下文 | 慢发生时没有捕到关系,需继续取证而非直接排除 |
| 数据库以外的等待 | 连接池、请求分段、驱动和应用记录 | 数据库执行阶段已经占主要耗时 |
先用EXPLAIN FORMAT=TREE检查固定SQL的估算计划;需要实际执行信息时,在实验环境对同一查询使用EXPLAIN ANALYZE FORMAT=TREE。后者会真实执行SELECT ,并增加测量开销,不能当作纯静态检查。EXPLAIN ANALYZE说明
这轮不要直接改索引。先保存计划中的访问方式、筛选和排序,再核对估算与实际。实际时间需要结合循环次数解释,子节点与父节点也可能包含重叠耗时。单次计划快,并不能反证之前并发时段不存在等待。
若怀疑行锁,在等待发生期间执行下面的只读观察。账号需要这些Performance Schema表的读取权限:
sql
SELECT w.REQUESTING_THREAD_ID AS waiting_thread,
w.BLOCKING_THREAD_ID AS blocking_thread,
r.OBJECT_SCHEMA, r.OBJECT_NAME,
r.INDEX_NAME, r.LOCK_TYPE, r.LOCK_MODE, r.LOCK_DATA
FROM performance_schema.data_lock_waits w
JOIN performance_schema.data_locks r
ON r.ENGINE = w.ENGINE
AND r.ENGINE_LOCK_ID = w.REQUESTING_ENGINE_LOCK_ID
WHERE r.OBJECT_SCHEMA = 'nanfeng_mysql_lab'
AND r.OBJECT_NAME = 'nf_order_bench';
这里的线程ID不是客户端连接ID,需要时通过performance_schema.threads映射PROCESSLIST_ID。等待结束后查询可能为空,因此快照要保存时间,不能把"刚才没查到"等同于"一直没有"。data_lock_waits字段与用途
想自行验证,可以开A、B两个连接操作同一实验表:A在START TRANSACTION后将id=42的total_amount加1,暂不提交;B开启事务,对id=42执行SELECT ... FOR UPDATE,第三个连接采集等待关系。B可先保存@@SESSION.innodb_lock_wait_timeout,临时设为30秒,结束后恢复。
采集完立即在A执行ROLLBACK;B返回或报超时后也执行ROLLBACK。锁等待超时不保证整个事务已经回滚,不能关掉结果提示就算处理完。InnoDB错误处理
这个练习证明的是锁定读等待现象,不能套到所有普通一致性读上。慢日志的Lock_time也不是InnoDB全部等待的总表;要用等待关系补足它表达不了的上下文。
五、把AI输出限制成可验收的诊断卡
先在本地缩小日志范围,再去掉敏感标识。提交给工具的是有上下文的最小证据集:结构、索引、代表参数分布、日志、计划、等待快照、缺失项,而非整台数据库的日志文件。
建议任务约定如下。这是待使用的提示模板,没有声称已经调用模型:
text
请根据所附实验记录生成诊断卡:
1. 已观察事实:每项注明对应材料。
2. 最多三个候选原因:说明支持证据与反证。
3. 下一步验证:优先选择能区分候选原因的最小检查。
4. 结论边界:版本、数据分布、并发条件、缺失指标。
数据缺失就写未知;不得补造执行时间或优化收益。
先诊断,不生成生产配置变更或自动执行动作。
AI建议"增加联合索引",就要求它指出具体减少哪部分工作;如果现场证据是同一主键上的事务等待,索引建议至少不能独立解释这个现象。AI建议"增加连接池",也要先说明数据库是否还有容量,以及连接池等待是不是结果而不是根因。
把这些追问留在诊断卡里,后续接手的人就能看懂"为什么做这个实验",而不只得到一个结果截图。
六、为下一次复现设计一个最小对照
证据收集完,不一定马上知道根因。此时可以把下一步写成一个明确的问题:"保持SQL、数据和索引不变,取消持锁事务以后,等待现象是否消失?"问题比"再优化一下数据库"具体,也更容易判断实验是否回答了它。
对照至少保留两个维度:执行条件和业务结果。执行条件包括参数、并发、连接行为、缓存状态、采集方式;业务结果则是相同顺序的返回ID及必要字段。不要在候选方案里悄悄去掉排序或缩短时间范围,再把少做的工作算成优化收益。
如果怀疑某个用户的数据量偏大,可选几个数据量不同的用户作为后续样本,并写明选择理由。这不等于已经模拟了真实业务分布,只是在验证"参数分布影响表现"这个假设。固定用户的实验与面向全体用户的结论,应保留范围上的区别。
如果怀疑应用连接池,把数据库查询耗时与拿连接的等待分别观察。等待连接变长可能与下游事务迟迟不结束有关;盲目增加连接数,反而可能把更多工作同时压到数据库。下一项实验应回答队列从哪里产生,而不是先把某个上限改大。
计划采集本身也要固定:不能一组使用客户端计时,另一组使用带分析开销的执行计划时间,最后直接计算提升比例。用于诊断的材料和用于验收的指标可以互相补充,但必须说明各自口径。
这些对照只需选择最能区分当前候选原因的一项。得到新证据以后更新诊断卡,再决定后续实验,避免同一轮叠加多个变化后失去解释能力。
七、交付给下一轮实验的是什么
Day17结束时,可以还没有最终修复方案,但应该已经缩小到可验证的假设。验收清单如下:
- SQL与有序结果基线保存完整,参数没有偷偷变化。
- 采集窗口、实例、会话阈值、索引状态可以复查。
- 日志聚合没有冒充全量业务统计。
- 计划与等待证据说明了采集时机和限制。
- AI输出中的事实能对应原始材料,缺失项仍然标为缺失。
- 所有日志设置恢复,实验事务结束,超时设置恢复。
下一轮只改变一个因素,例如采用Day16的候选索引,再对相同负载验证。若计划工作量减少而接口延迟没有改善,就沿已保存的分段计时继续查;不能仅凭一张漂亮计划宣布任务完成。
讨论题:你交接一次性能问题时,最希望上一位排查者留下的是SQL、执行计划,还是能复现问题的完整条件?为什么?
下一篇:MySQL 性能 + AI 调优实战。我们把证据卡变成带正确性、延迟分布、写入代价和回退条件的完整实验。
欢迎关注「楠风测开」,一起把测试工程师复习日记写成能接着做下去的工程记录。