Skip to content

Commit 47408b2

Browse files
committed
fix(cases): 第三轮架构师审核修复 6 处技术错误
第三轮深入审核发现 28 个问题,本轮修复 6 个确凿的'会让读者跑出非预期结果'的技术错误,剩余 22 个偏文档风格/版本细节问题留待后续。 ## 严重问题(5) **#R1 案例 84 文档 bad.sql 是占位符** - 原 bad.sql 第 52-53 行:'SELECT ... FROM t1, t2 LIMIT 50000' - 't1, t2' 是占位符不是真实表,直接复制运行报 Table doesn't exist - 替换为指向 sql/cases/84-tidb-statistics/bad.sql 真实可执行 SQL **#R2 案例 88 bad/good WHERE 条件不一致** - bad.sql 删 status=0 - good.sql 改删 status=1 - 条件不一致破坏对比意义 - good 改为同条件 status=0 LIMIT 1000 **#R3 案例 90 tidb_mem_quota_query 数字矛盾** - 第 13 行:'HashAgg 吃了将近 2GB,tidb_mem_quota_query 设置为 1GB' - bad.sql 第 43 行:'SET SESSION tidb_mem_quota_query = 104857600 (100MB)' - 2GB/1GB 与 100MB 实际设置直接矛盾 - 改为:'HashAgg 吃了将近 200MB,tidb_mem_quota_query 设置为 100MB' **#R4 案例 91 Index Join 性能计算错误** - 原描述:'90万次比较远小于 10万行全扫 + 构建哈希' - 在 TiDB 分布式场景下,Index Join 的 10万次 IndexRangeScan 每次是 RPC 调用(~0.5-1ms),总开销约 50 秒 - Hash Join 反而远优于 Index Join(一次全表扫 ~100ms) - 这是案例主题的关键事实错误 - 改为:'10万次 RPC 是真正瓶颈,Hash Join 远优' **#R5 案例 88 MySQL 变量名在 TiDB 中不生效(3处)** - max_execution_time(MySQL 8.0 变量)→ tidb_max_execution_time_ms(TiDB 7.x 变量) - 涉及 L145 SET、L165 文档说明、L222 推荐表 - TiDB 不识别 max_execution_time,配置无效 ## 中等问题(1) **#R6 案例 99 innodb_lock_wait_timeout 默认值错误** - 原表:'MySQL 默认 50s | TiDB 默认 50s' - 实际 TiDB 7.x 默认 3s(不是 50s) - v6.x 及以下才是 50s - 改为:'TiDB 7.x 默认 3s;v6.x 及以下默认 50s' **#R7 案例 65 next-key lock 描述补充** - '锁定行数 ~20,031' 是记录锁命中数,RR 下还会加间隙锁 - 补充:'实际含间隙锁,锁范围更大' ## 验证 - npm run docs:build 成功 - npm run docs:pdf 生成 55.0 MB / 748 页 / 113 书签 - 6 个文件修改,+18 / -14 行
1 parent 6719510 commit 47408b2

7 files changed

Lines changed: 18 additions & 14 deletions

File tree

docs/cases/tidb/84-tidb-statistics.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ SHOW STATS_META WHERE db_name = 'sql_treasure' AND table_name = 't_stats';
4848
-- 2. 模拟"先查后改":先执行查询建立基线
4949
EXPLAIN SELECT * FROM t_stats WHERE status = 1 AND city = 'Beijing';
5050

51-
-- 3. 插入 50000 行新数据(但不更新统计信息
52-
INSERT INTO t_stats (user_id, status, city, amount, created_at)
53-
SELECT ... FROM t1, t2 LIMIT 50000;
51+
-- 3. 插入 50000 行新数据(状态全为 0,不更新统计信息
52+
-- 使用笛卡尔积生成 50000 行:t1(200行) x t2(250行) = 50000
53+
-- 完整可执行 SQL 见 sql/cases/84-tidb-statistics/bad.sql
5454

5555
-- 4. 统计过期后再次 EXPLAIN——优化器仍用旧统计估算
5656
EXPLAIN SELECT * FROM t_stats WHERE status = 1 AND city = 'Beijing';

docs/cases/tidb/88-tidb-gc.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,14 @@ SELECT * FROM mysql.tidb WHERE variable_name IN ('tikv_gc_safe_point', 'tikv_gc_
142142

143143
-- 3. 设置事务超时,防止长事务
144144
SET SESSION tidb_idle_transaction_timeout = 300;
145-
SET SESSION max_execution_time = 10000;
145+
SET SESSION tidb_max_execution_time_ms = 10000; -- 10 秒(TiDB 7.x;MySQL 变量 max_execution_time 在 TiDB 中不生效)
146146

147147
-- 4. 查看 GC 历史
148148
SELECT * FROM mysql.tidb WHERE variable_name LIKE 'tikv_gc%';
149149

150150
-- 5. 合理的事务设计:短事务 + 分批处理
151-
-- 将大量 DELETE 拆分为小批次
152-
DELETE FROM t_gc_test WHERE status = 1 LIMIT 1000;
151+
-- 将大量 DELETE 拆分为小批次(与 bad.sql 同条件 status=0)
152+
DELETE FROM t_gc_test WHERE status = 0 LIMIT 1000;
153153
-- 每批次提交后 GC 可推进
154154
```
155155

@@ -162,7 +162,7 @@ DELETE FROM t_gc_test WHERE status = 1 LIMIT 1000;
162162
**方案二:事务超时防护**
163163

164164
- `tidb_idle_transaction_timeout`:事务空闲超时。连接在事务中空闲超过指定秒数,自动回滚。防止 "BEGIN 后忘记 COMMIT" 导致的长事务。
165-
- `max_execution_time`:单条 SQL 最大执行时间。防止大 SQL 撑出长事务。
165+
- `tidb_max_execution_time_ms`(TiDB 7.x;MySQL 8.0 的 `max_execution_time` 在 TiDB 中不生效):单条 SQL 最大执行时间(毫秒)。防止大 SQL 撑出长事务。
166166

167167
**方案三:分批处理**
168168

@@ -219,7 +219,7 @@ DELETE FROM t_gc_test WHERE status = 1 LIMIT 1000;
219219
| `tikv_gc_enable_compaction_filter` | `false` | `true`(v5.0+) | 写入密集型场景,利用 compaction 辅助回收 |
220220
| `tikv_gc_auto_concurrency` | `true` | `true` | 所有场景(让 TiDB 自动调整) |
221221
| `tidb_idle_transaction_timeout` | `0`(不限制) | `300` | 所有在线业务(防止忘记 COMMIT) |
222-
| `max_execution_time` | `0`(不限制) | `10000` | 在线 OLTP 业务(防止慢 SQL 撑出长事务) |
222+
| `tidb_max_execution_time_ms` | `0`(不限制) | `10000` | 在线 OLTP 业务(防止慢 SQL 撑出长事务,TiDB 7.x 变量名|
223223

224224
---
225225

docs/cases/tidb/90-tidb-memory-oom.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
ERROR 1105 (HY000): Out Of Memory Quota!
1111
```
1212

13-
你注意到这条 SQL 是一个大表(3000 万行)的 `GROUP BY` 聚合,有 50000 个分组,执行时 HashAgg 算子吃了将近 2GB 内存——而当前 `tidb_mem_quota_query` 设置为 1GB。你意识到:这不是数据量本身的问题,而是 **TiDB 的内存控制机制**在保护数据库进程的同时,也无情地打断了你的查询。
13+
你注意到这条 SQL 是一个大表(3000 万行)的 `GROUP BY` 聚合,有 50000 个分组,执行时 HashAgg 算子吃了将近 **200MB 内存**——而当前 `tidb_mem_quota_query` 设置为 **100MB**(演示环境特意调低)。你意识到:这不是数据量本身的问题,而是 **TiDB 的内存控制机制**在保护数据库进程的同时,也无情地打断了你的查询。
1414

15-
更棘手的是,同样这条 SQL 在白天偶尔能成功,偶尔又报 OOM。事后你检查监控时发现:白天集群空闲时内存足够,但凌晨恰好有备份任务在跑,总内存紧张,HashAgg 没分到足够空间就被杀掉了。
15+
更棘手的是,同样这条 SQL 在白天偶尔能成功,偶尔又报 OOM。事后你检查监控时发现:白天集群空闲时内存足够,但凌晨恰好有备份任务在跑,总内存紧张,HashAgg 没分到足够空间就被杀掉了。在生产环境中默认 `tidb_mem_quota_query` 是 1GB,而聚合数据量更大时会突破这一阈值。
1616

1717
::: warning 真实场景
1818
在生产 TiDB 集群中,OOM 错误通常不是"表太大"的信号,而是 **并发 + 内存配置** 的组合问题。同一条 SQL 在低并发时可能正常运行,但当 10 个连接同时执行 HashAgg 时,每个都试图申请 1GB=10GB,远超 TiDB Server 的实际可用内存。

docs/cases/tidb/91-tidb-join-algorithms.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,12 @@ Build 阶段:
145145
146146
Probe 阶段:
147147
对每行 a_val 值 → IndexRangeScan(t_join_c, idx_val) → 精确查找
148-
每次查找: O(log 500) ≈ 9 次比较
149-
总开销: 100000 × 9 ≈ 90 万次比较(远小于 10 万行全扫 + 构建哈希)
148+
⚠️ TiDB 分布式关键: 每次 IndexRangeScan 都是一次 TiDB → TiKV 的 RPC 调用
149+
每次 RPC: ~0.5-1ms(含网络+TiKV coprocessor 调度)
150+
总开销: 100,000 × 0.5ms ≈ 50 秒(远大于 Hash Join 的 ~100ms)
151+
152+
CPU 维度看: 100,000 × log₂(500) ≈ 90 万次比较,理论很快
153+
但 TiDB 分布式维度: 10 万次 RPC 是真正瓶颈,Hash Join 远优
150154
```
151155

152156
::: tip 核心认知

docs/cases/tidb/99-tidb-lock-deep.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ TiDB (悲观模式):
7575
| 锁信息查询 | `performance_schema.data_locks` | `information_schema.data_lock_waits` |
7676
| 锁粒度 | Record Lock / Gap Lock / Next-Key Lock | 基于 Key 的分布式锁 |
7777
| 死锁检测 | InnoDB 自动回滚代价小的事务 | TiDB 自动检测并回滚一个事务 |
78-
| 锁超时 | `innodb_lock_wait_timeout`(默认 50s) | `innodb_lock_wait_timeout`默认 50s) |
78+
| 锁超时 | `innodb_lock_wait_timeout`(默认 50s) | `innodb_lock_wait_timeout`TiDB 7.x 默认 3s;v6.x 及以下默认 50s) |
7979
| FOR UPDATE NOWAIT | MySQL 8.0+ | 支持(兼容 MySQL 8.0) |
8080
| FOR UPDATE SKIP LOCKED | MySQL 8.0+ | 支持(兼容 MySQL 8.0) |
8181
| 分布式锁可见性 | 单节点,所有锁在同一实例 | 多 TiKV 节点,需聚合查询 |

docs/cases/transaction/65-select-for-update-scope.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ SELECT * FROM t_product WHERE id = 100 FOR UPDATE;
168168
|---|---|---|---|
169169
| type | ALL | ref | const |
170170
| 扫描行数 | ~100,155 | ~20,031 | 1 |
171-
| 锁定行数 | 全表(10 万行) | ~20,031 | 1 |
171+
| 锁定行数 | 全表(10 万行) | ~20,031(实际含间隙锁,锁范围更大) | 1(next-key lock 仅锁单行+前后间隙) |
172172
| 并发更新 | 全部阻塞 | 仅电子行阻塞 | 仅 1 行阻塞 |
173173
| 锁等待概率 | 极高 || 极低 |
174174

docs/public/sql-lab-cases.pdf

28.7 KB
Binary file not shown.

0 commit comments

Comments
 (0)