24 KiB
R01-E 批查询与会话未读数:独立复验报告(第三~十一节)
日期:2026-09-18
分支:refactor/r01-structure-performance
HEAD / upstream:b893ed67f078eedb0c4b8a03341311bd79342354(ahead/behind = 0 / 0)
本轮性质:独立复验 —— 不改生产代码、不改迁移、不暂存、不提交、不推送
上游依据:docs/r01-e-batch-query-unread-count-performance-report.md(实施与测试加固报告)
0. 结论
R01-E独立代码与真实MySQL性能复验通过,可以进入精确暂存与独立提交检查点。
| 复验项 | 结论 |
|---|---|
| 独立查询预算(E1 = 20/20/20,E2 = 3/3/3) | 通过(H2 与真实 MySQL 双侧实测) |
| E1 collector 完整输出 H2/MySQL 深比较 | 通过(逐字段、含 null/时间/集合顺序/关联数据) |
| E2 会话未读 H2/MySQL 深比较(10 项语义) | 通过 |
MySQL EXPLAIN FORMAT=JSON |
通过(基础表命中既有索引,无需新增 V46) |
| 副作用与隔离(25 张表快照 + 反证 + 零残留) | 通过 |
| OBS-E01 | 保留既有语义,作为产品负责人待决策项上报 |
独立复验时全量 mvn test / mvn clean verify |
2787 / 0 / 0 / 0 |
提交前收口 mvn clean verify |
2788 / 0 / 0 / 0(增加 1 个 EXPLAIN 变异门禁) |
| OpenAPI | 176 paths / 214 operations,openapi-rc.json 未改 |
| 生产代码 diff | 0 个文件 |
独立复验新发现(3 项,均如实记录):
F1 实施测试的 EXPLAIN 断言 doesNotContain("\"table_name\": \"messages\", ...") 是空断言——
MySQL 的 EXPLAIN JSON 里 table_name 存的是别名(本语句为 m / mr),
"table_name": "messages" 永不出现,故该断言无论如何都会通过。结论本身(当时夹具下确实未全扫)成立,
但断言不具备鉴别力。提交前收口已将实施测试改为解析 JSON 树,按别名分别定位
m / mr / ranked,校验 access_type、key、temporary 与 filesort,并以错误索引、
基础表 ALL、缺失节点三类变异证明断言有效。
F2 H2 与 MySQL 对同一 JSON 列的回读表示不同(详见 §4.2),已记录原始值并按语义归一后比较。
F3 实施报告 §9 记 using_filesort=false;本轮在真实规模夹具下实测 using_filesort=true
(窗口函数 ROW_NUMBER() 的排序代价),详见 §6.4。
1. 复验范围与不重叠声明
- 从第三~十一节执行,未重做第一、二节库存,未修改任何生产代码。
- 复用既有文件(未重复创建):
mydreamjob-server/src/test/java/com/mydreamjob/common/r01e/R01EQueryBudgetAcceptanceTest.java(实施测试,11 项)mydreamjob-server/src/test/java/com/mydreamjob/common/r01e/R01EMySqlQueryCountAndExplainTest.java(实施测试,4 项)docs/r01-e-batch-query-unread-count-performance-report.md
- 独立测试不复用实施测试的任何夹具、断言或辅助方法:夹具、快照(25 张表、列集合不同)、 计数 DataSource、EXPLAIN 解析均为独立实现。
2. 本轮新增文件(未跟踪)
mydreamjob-server/src/test/java/com/mydreamjob/common/r01e/R01EIndependentCrossDatabaseVerificationTest.java 独立测试,16 项
mydreamjob-server/src/test/java/com/mydreamjob/common/r01e/R01EIndependentSideEffectIsolationTest.java 独立测试,10 项
docs/r01-e-independent-verification-20260918.md 本报告
生产代码:0 个文件(git diff --stat 为空)。
3. 独立查询预算验证(任务 1)
方法:独立实现的 CountingDataSource 包装真实 DataSource,记录真实 JDBC 语句文本与条数;
两侧在夹具灌入完成之后才 reset(),故计数不含测试准备与清理 SQL(断言实测语句全部为 SELECT)。
3.1 E1:1 / 10 / 50 条投递
| 夹具 | H2 实测 | MySQL 实测 | 目标 |
|---|---|---|---|
| 与实施报告同基线的最小夹具(2 用户 + 1 公司 + N 岗位 + N 投递) | [20, 20, 20] | [20, 20, 20] | 20/20/20 ✔ |
| 本类丰富夹具(12 个 section 全部有数据)+ N 投递 | [22, 22, 22] | [22, 22, 22] | 恒定 ✔ |
- 两者都不随记录数增长;与实施报告「20/20/20」的结论独立复现一致。
- 丰富夹具为 22(比最小夹具多 2):多出的是「本人持有公司 → offers 企业侧分支」以及 「存在会话 → conversations/messages 两条批量查询」这两个按分支固定的查询,仍与 N 无关。
- 证据:
target/r01e-independent/e1-budget.txt、e1-budget-rich.txt。
3.2 E2:1 / 20 / 100 会话
| 会话数 | H2 实测 | MySQL 实测 | 目标 |
|---|---|---|---|
| 1 | 3 | 3 | 3 ✔ |
| 20 | 3 | 3 | 3 ✔ |
| 100 | 3 | 3 | 3 ✔ |
- 实测 SQL 文本恰好为 3 条,形态固定且可辨认:
select ... from conversation_members ... where user_id=?select ... from conversations ... where id in (?,...)- 分组未读查询(含
ROW_NUMBER())——只出现 1 次
- 证据:
target/r01e-independent/e2-budget.txt(含两侧完整 SQL 文本)。
3.3 空会话页不执行未读查询
| 断言 | H2 | MySQL |
|---|---|---|
listConversations(无会话用户) 返回空 |
✔ | ✔ |
| 实测语句数 | 1 | 1 |
该语句不含 ROW_NUMBER |
✔ | ✔ |
含 row_number 的语句数 |
0 | 0 |
即:空会话页只发出一条成员关系查询后短路返回,不下发未读聚合查询。
4. E1 H2 / MySQL 深比较(任务 2)
4.1 方法
同一个 JVM 内同时持有两个 Spring 上下文:
- H2 侧:测试自身上下文,
jdbc:h2:mem:r01e_ind;MODE=MySQL(本类独占的内存库,Flyway 全量迁移); - MySQL 侧:
@BeforeAll手工引导的第二个上下文(Testcontainersmysql:8.4,mysql-it配置,WebApplicationType.NONE,命令行参数覆盖数据源),Flyway 在容器内真实执行 45 个迁移。
两侧灌入同一份等价夹具(显式时间戳、无 CURRENT_TIMESTAMP 依赖),覆盖 全部 12 个 section,
并额外放入一个「外人」用户的同类数据。
先决条件已实证:bothEnginesAreLiveAndFixtureIdsAgree 断言两侧 27 个自然键 → 主键映射完全相等
(同一 JVM、两侧均为全新建库),故后续可直接比较含主键的完整输出。
4.2 深比较结果
canonicalResult(...) 把 collector 的完整 section 输出规范化为确定性文本:显式呈现 null、
时间按 LocalDateTime 呈现、List 保持原序(集合顺序)、Map 按 key 排序、BigDecimal 去尾零。
e1-h2.txt == e1-mysql.txt (均 6213 字节,逐字符相同)
JSON 列的表示差异(F2,如实记录)——e1-raw-json.txt 原文:
h2: profile.skills="{\"java\":3,\"sql\":2}" | preferences.preferredCities="[\"北京\",\"上海\"]"
mysql: profile.skills={"sql": 2, "java": 3} | preferences.preferredCities=["北京", "上海"]
- H2(MODE=MySQL)把 JSON 列回读为被转义的 JSON 字符串标量;
- MySQL 8回读为 JSON 对象字面量(键重排、冒号后加空格)。
- 两者语义相同、表示不同:比较的是语义。规范化对确实是 JSON 的字符串递归解码后按键排序,
非 JSON 字符串原样保留(
content、coverLetter等不受影响)。
反证:把 dump 中任意一个字段值("张三" → "张四")置换后断言必须不再相等——通过,
说明规范化是逐字段敏感的,而不是只比条数、hash 或长度。
4.3 其他断言
| 断言 | 结果 |
|---|---|
| section 顺序 | 精确等于 profile, preferences, resumes, applications, application-timelines, interviews, interview-timelines, offers, offer-timelines, feedback, favorites, notifications, conversations, messages, candidate-scoring, scoring-appeals ✔ |
| 关联数据 | 应用↔时间线、面试↔时间线、Offer↔时间线、会话↔消息的分组关系随主键一并跨库一致 ✔ |
| 其他用户数据不得混入 | dump 中不含 李四 / 外人的简介 / 外人简历 / 外人求职信 / 外人评价 / 外人通知 / 外人会话 / out-m1 / 外人Offer / 外人理由 / 外人面试 / outsider ✔ |
| 跨库自有行数 | 23 张表的自有行数两侧完全一致 ✔ |
非 ACTIVE 简历、read_at 为 null / 非 null 的通知、score 为 null 的维度分、tags 为 null 的评价、cover_letter 为 null 的投递 |
均已覆盖并通过 ✔ |
5. E2 H2 / MySQL 深比较(任务 3)
10 项语义逐条覆盖,两侧结果逐值一致(会话列表完整视图规范化后逐字符相同):
| # | 语义 | H2 | MySQL | 结果 |
|---|---|---|---|---|
| 1 | 本人发送未读按现状计入(OBS-E01) | c1 = 2(本人 1 + 他人 1) | c1 = 2 | ✔ 一致 |
| 1b | 同上,把本人那条标记已读后 | 1 | 1 | ✔ |
| 2 | 他人发送未读计入 | ✔ | ✔ | ✔ |
| 3 | message_reads 排除 |
读 1 条后 2 → 1 | 同 | ✔ |
| 3b | 另一读者的已读记录不影响本人 | 不受影响 | 不受影响 | ✔ |
| 4 | RECALLED 排除 |
撤回 1 条后 2 → 1 | 同 | ✔ |
| 5 | 多会话分组 | c1=2, c2=1, c3=1 | 同 | ✔ |
| 6 | 非当前用户会话不返回 | 只返回 c1/c2/c3;cOutsider 不在 | 同 | ✔ |
| 7 | 聚合缺失补 0 | 全部撤回/已读时聚合缺行 → 0 | 同 | ✔ |
| 8 | 最近第 500 条计入、第 501 条排除 | 501 条未读 → 500;把最旧一条标已读 → 仍 500;把最新一条标已读 → 499 | 同 | ✔ |
| 9 | created_at 相同的排序语义 |
同刻 3 条 → 3;其中 1 条已读 → 2;同刻 501 条 → 500 | 同 | ✔ |
| 10 | 空及重复 conversationIds |
重复 [c1,c1] 只产 1 行且与 [c1] 逐值相同;空列表由 Service 短路、不下发 IN () |
同 | ✔ |
第 8 项证明方式:不止断言「等于 500」,而是通过「标已读最旧一条 → 不变」证明最旧一条本就在窗口之外, 「标已读最新一条 → 499」证明窗口保留的是最新一侧。
第 9 项的诚实边界:ROW_NUMBER() ... ORDER BY m.created_at DESC 没有 tiebreaker,
故当 501 条 created_at 完全相同时,「保留哪 500 条」由引擎决定;
但窗口计数与引擎无关,恒为 500——两侧实测一致。此点已计入遗留风险(R2)。
6. MySQL EXPLAIN FORMAT=JSON(任务 4)
被测语句与 MessageRepository.countUnreadByConversationIds 逐字相同,绑定同一批参数。
夹具:先铺入 500 个背景会话 × 20 条 = 10 000 条消息(不属于查询范围),再替换目标会话——
否则 messages 表几乎只含查询范围内的行,MySQL 选择全扫是正确的代价决策,
却会让 EXPLAIN 失去验收意义。
6.1 逐表结果(messages / message_reads / 派生结果 ranked 分开报告)
注意:MySQL 的
EXPLAIN FORMAT=JSON中table_name是别名。 被测语句的别名为messages m、message_reads mr、派生表ranked, 故表节点分别为m/mr/ranked。
| 规模 | selectivity | ranked(派生结果) |
messages(别名 m) |
message_reads(别名 mr) |
temporary | filesort |
|---|---|---|---|---|---|---|
| 1 会话 × 3 条 | 0.01% | ALL(派生结果顺序扫描)· rows=3 | ref · idx_messages_conversation_created · rows=3 |
eq_ref · uk_message_reads · rows=1 |
true | true |
| 20 会话 × 3 条 | 0.27% | ALL · rows=60 | range · idx_messages_conversation_created · rows=60 |
eq_ref · uk_message_reads · rows=1 |
true | true |
| 100 会话 × 3 条 | 1.36% | ALL · rows=300 | range · idx_messages_conversation_created · rows=300 |
eq_ref · uk_message_reads · rows=1 |
true | true |
| 1 会话 × 600 条(> 500 窗口) | 2.73% | ALL · rows=600 | ref · idx_messages_conversation_created · rows=600 |
eq_ref · uk_message_reads · rows=1 |
true | true |
判读:
- 派生结果
ranked的access_type = ALL必须准确表述为「派生结果顺序扫描」—— 这是 MySQL 对ROW_NUMBER()派生表物化后顺序读取物化结果的固有形态,possible_keys/key均为null(派生表上不存在索引)。不得笼统写成「无全表扫描」。 messages基础表在四个验收断言规模下命中既有索引idx_messages_conversation_created(ref/range); 更高选择性探测会转为ALL,详见下一节,不能笼统表述为“没有全表扫描” ✔message_reads基础表在所有规模下均为eq_ref命中uk_message_reads✔ (possible_keys同时列出idx_message_reads_message/idx_message_reads_reader)possible_keys、key、rows_examined_per_scan、filtered、using_temporary_table、using_filesort均已逐规模记录,原文见target/r01e-independent/e2-explain.txt。
6.2 代价拐点(选择性探测,只记录不判定)
| 规模 | selectivity | messages 访问方式 |
|---|---|---|
| 2 会话 × 600 条(1 200 行) | 5.45% | range · idx_messages_conversation_created(命中索引) |
| 5 会话 × 600 条(3 000 行) | 13.63% | ALL · key=null · rows=13 093(转为全扫) |
| 20 会话 × 600 条(12 000 行) | 54.53% | ALL · key=null · rows=22 016(转为全扫) |
结论:拐点被夹逼在 5.45% 与 13.63% 之间。也就是说,
只有当「被查询的会话集合占 messages 全表 6% 以上」时,MySQL 才会放弃索引改走全扫——
这是代价决策,不是缺索引。真实场景中一个用户的会话列表远低于此比例,
故本项判定 PASS,不新增 V46(严格遵守指令)。
6.3 using_temporary_table = true 的归属
true 出现在 grouping_operation 层,是 GROUP BY ranked.conversation_id 的固有代价,
与派生表物化同源,不是新增索引可以消除的。
6.4 using_filesort 的更正(F3)
实施报告 §9 记录 using_filesort=false(其夹具为 1 会话 × 200 条)。
本轮在带背景数据的真实规模夹具下,1/20/100/600 四档全部实测 using_filesort=true——
来源是窗口函数 ROW_NUMBER() OVER (PARTITION BY conversation_id ORDER BY created_at DESC) 的排序。
这是既有实现的固有代价,本轮不改动,仅如实更正记录。
7. 副作用与隔离(任务 5)
独立测试 R01EIndependentSideEffectIsolationTest,运行在共享的 integration H2 库上(与实施测试同库,
因此可以直接观测实施测试的夹具残留)。
7.1 逐字段快照(25 张表,独立于实施测试的 11 张)
users(6 列)、user_roles、companies、jobs、applications、application_status_history、
interviews、interview_status_history、offers、offer_status_history、candidate_feedback、
job_favorites、notifications、audit_logs、auth_tokens、conversations、conversation_members、
messages(含 version)、message_reads、student_profiles、student_preferences、resume_versions、
candidate_dimension_scores、candidate_scoring_appeals、candidate_scoring_appeal_history
| 用例 | 结论 |
|---|---|
collector.collect() 前后 |
25 张表逐字段零变化 ✔ |
listConversations() 前后 |
25 张表逐字段零变化 ✔ |
listConversations + getConversation + collect 组合 |
25 张表逐字段零变化 ✔ |
读取路径零写:夹具覆盖全部 12 个 section(collect 走满所有分支),快照仍零变化。
7.2 通知 / 审计 / RabbitMQ
| 项 | 结论 |
|---|---|
notifications 行数与逐字段内容 |
读前后完全相等 ✔ |
audit_logs 行数与逐字段内容 |
读前后完全相等 ✔ |
| RabbitMQ | 以 @MockBean RabbitTemplate 注入,读路径结束后 verifyNoInteractions(rabbitTemplate) 通过——零 AMQP 交互 ✔ |
| MQ 配置 | mysql-it / integration-test 均置 app.notification.mq-enabled: false、spring.rabbitmq.enabled: false,NotificationConsumer 条件装配关闭 ✔ |
7.3 反证:快照必须真的能发现变化
| 反证用例 | 期望 | 结果 |
|---|---|---|
修改一行(users.nickname) |
断言必须失败 | AssertionError,含 users ✔ |
新增一行(notifications) |
断言必须失败 | AssertionError,含 notifications ✔ |
删除一行(message_reads) |
断言必须失败 | AssertionError,含 message_reads ✔ |
仅单列变化(messages.status → RECALLED) |
断言必须失败 | AssertionError,含 messages ✔ |
若无此反证,「零变化」可能只是没在比。
7.4 夹具零残留
| 检查 | 结果 |
|---|---|
本类自有夹具(r01eiso-%)退场后残留 |
0(@AfterEach 精确清理 + @AfterAll 门禁)✔ |
实施测试的夹具(r01e-%,排除本类前缀)残留 |
0 ✔ |
实施测试遗留的 messages / r01e-co / r01e-job |
0 ✔ |
关于此前「缺 @AfterEach 导致 60 项失败」的专项复验:H2 内存库按 JVM 共享,
只有让 R01EQueryBudgetAcceptanceTest 先于独立类运行,残留检查才具备鉴别力。
为消除运行顺序的不确定性,本轮追加一次逆序运行:
mvn -o -Dsurefire.runOrder=reversealphabetical -Dtest="R01EQueryBudgetAcceptanceTest,R01EMySqlQueryCountAndExplainTest,R01EIndependentCrossDatabaseVerificationTest,R01EIndependentSideEffectIsolationTest" test
→ 实际顺序:R01EQueryBudgetAcceptanceTest(11) → R01EMySqlQueryCountAndExplainTest(4)
→ R01EIndependentSideEffectIsolationTest(10) → R01EIndependentCrossDatabaseVerificationTest(16)
→ 独立复验:Tests run: 41, Failures: 0, Errors: 0, Skipped: 0
→ 提交前收口:Tests run: 42, Failures: 0, Errors: 0, Skipped: 0(新增 EXPLAIN 变异门禁)
即:实施测试跑完后,独立类的 implementationFixturesLeaveNoResidue 与 @AfterAll 门禁
直接确认其夹具残留为 0,旧问题已修复且可被独立观测。
8. OBS-E01(任务 6)
观察项(本轮不改变既有语义,作为产品负责人待决策项上报):
本人发送且没有当前用户
message_reads记录的消息,计入本人未读数。
| 证据 | H2 | MySQL |
|---|---|---|
| 会话内「本人 1 条 + 他人 2 条,均未读」 | 未读 = 3(不是 2) | 3 |
| 会话内「他人 1 条 + 本人 1 条 + 1 条撤回」 | 未读 = 2 | 2 |
把本人发送的那条写入 message_reads |
未读减为 1 | 1 |
- 该行为源自未读查询的
WHERE中没有发送者过滤(只按conversation_id与读者message_reads判定)。 - 本轮未修改 SQL、未修改任何生产代码,仅将既有语义如实锁定并双侧取证。
- 若产品确认应排除本人发送的消息,属业务语义变更,需另立批次,不在本次性能收束范围内。
9. 门禁结果(任务完成后的规定命令)
| # | 命令 | Tests | Failures | Errors | Skipped | 结果 |
|---|---|---|---|---|---|---|
| 1 | mvn -Dtest="R01EQueryBudgetAcceptanceTest,R01EMySqlQueryCountAndExplainTest,R01EIndependentCrossDatabaseVerificationTest,R01EIndependentSideEffectIsolationTest" test |
41 | 0 | 0 | 0 | SUCCESS |
| 1b | 同上 + -Dsurefire.runOrder=reversealphabetical(残留门禁) |
41 | 0 | 0 | 0 | SUCCESS |
| 2 | mvn -Dtest="*PersonalData*Test,*Export*Test,*Messaging*Test,*Message*Test,*Conversation*Test,*Recall*Test" test |
150 | 0 | 0 | 0 | SUCCESS |
| 3 | 独立复验 mvn test |
2787 | 0 | 0 | 0 | SUCCESS |
| 4 | 独立复验 mvn clean verify |
2787 | 0 | 0 | 0 | SUCCESS |
| 5 | 提交前收口 mvn clean verify |
2788 | 0 | 0 | 0 | SUCCESS |
| 5 | python ..\docs\verify_openapi.py |
paths 176 / operations 214 | — | — | — | All checks passed! |
测试计数对账:独立复验为 2761(R01-C 阶段二基线)+ 26(独立新增:16 跨库 + 10 副作用)= 2787;
提交前收口再增加 1 个 EXPLAIN 变异门禁,最终为 2788 ✔
(实施测试的 11 + 4 = 15 已含在 2761 内,本轮未新增实施测试。)
9.1 实施测试 / 独立测试 明细
| 类别 | 测试类 | 项数 | 引擎 |
|---|---|---|---|
| 实施测试 | R01EQueryBudgetAcceptanceTest |
11 | H2 |
| 实施测试 | R01EMySqlQueryCountAndExplainTest |
4 | MySQL(Testcontainers) |
| 独立测试 | R01EIndependentCrossDatabaseVerificationTest |
16 | H2 + MySQL(同 JVM 双上下文) |
| 独立测试 | R01EIndependentSideEffectIsolationTest |
10 | H2(共享 integration 库) |
| 合计 | 41 |
9.2 Testcontainers / MySQL 实际执行证据
- Docker Server 29.6.2;
mysql:8.4容器日志实测:Container mysql:8.4 started in PT7.4093471S。 R01EMySqlQueryCountAndExplainTest标注@Testcontainers(disabledWithoutDocker = true)—— 若 Docker 不可用该类的 4 项会被 skip;实测 Skipped = 0,故容器真实启动并执行。R01EIndependentCrossDatabaseVerificationTest断言MySql.container.isRunning()为true, 并在 MySQL 上真实执行了 Flyway 全量迁移、跨库深比较、ANALYZE TABLE与EXPLAIN FORMAT=JSON。- 两类 MySQL 用例耗时分别为 122.5 s 与 17.2 s(
clean verify单次运行),与容器冷启动 + 万级夹具灌入相符。
10. 生产代码与契约不变性
| 项 | 结果 |
|---|---|
| 生产代码 diff | 0 个文件(git diff --stat 空) |
| V1–V45 迁移 / Flyway | 未修改(目录 git status 0 项,仍为 45 个版本文件) ✔ |
pom.xml / 依赖 |
未修改 ✔ |
ErrorCode |
未修改 ✔ |
| 公网接口 / DTO / OpenAPI 契约 | 未修改(176 / 214) ✔ |
docs/openapi-rc.json |
未修改 ✔ |
| 新增迁移 V46 | 未新增(EXPLAIN 未发现必须新增索引的情形) ✔ |
| Security / Token / 权限 / requestId | 未修改 |
| 评分、排名、推荐、申诉、邀约、Offer、撤回与已读语义 | 未修改 |
| 分布式缓存 / 冗余未读列 / 异步最终一致未读数 | 未引入 |
11. Git 状态(未暂存 / 未提交 / 未推送)
| 项 | 结果 |
|---|---|
| 分支 / HEAD | refactor/r01-structure-performance / b893ed67f078eedb0c4b8a03341311bd79342354(未变) |
| HEAD 与 upstream | 0 / 0 ✔ |
暂存区 git diff --cached --name-only |
空 ✔ |
| 本轮新增(未跟踪) | 2 个独立测试类 + 本报告 |
| 既有未跟踪文件 | 完整保留,未删除、未移动 |
确认:全程未执行 git add / commit / push,未执行 reset / restore / checkout / clean。
12. 遗留风险
| # | 风险 | 说明 |
|---|---|---|
| R1 | OBS-E01 语义 | 本人发送且无本人 message_reads 的消息计入本人未读数。既有实现如此,本轮未改;如需变更属业务语义变更,须另立批次 |
| R2 | created_at 并列无 tiebreaker |
未读窗口的 ORDER BY m.created_at DESC 无二级排序键。并列跨越 500 边界时「保留哪 500 条」由引擎决定(计数恒为 500,两侧一致)。messages.created_at 为秒级 DATETIME,高并发同秒写入可触发 |
| R3 | 派生表物化 | MySQL 对 ROW_NUMBER() 派生表物化,ranked 为派生结果顺序扫描(access_type=ALL),且 using_temporary_table=true、using_filesort=true 为固有代价。大会话(> 每表 6% 选择性)下 messages 转为全扫,属代价决策 |
| R4 | 测试自污染 | 本轮测试均按前缀精确清理;若将来新增同类夹具而不清理,仍可能污染共享 H2 库(R01-C 已建隔离门禁) |
| R5 | 实施测试断言鉴别力(F1) | 原 EXPLAIN 断言为空断言;提交前收口已改为 JSON 结构断言,并以三类变异证明有效 |
R01-E独立代码与真实MySQL性能复验通过,可以进入精确暂存与独立提交检查点。