docs+chore(projects): 索引 EXPLAIN 分析 + 修正过时注释

- BizOrgMapper: selectSponsorOrgOptions/selectExecutorOrgOptions 注释
  写 "走 idx_org_user_type_name", EXPLAIN 实际命中 idx_org_type_status
  (优化器小数据下选小索引), 改为实测索引 + Using where 描述
- manager_projects.md §9.3: biz_project_assign 唯一约束状态标 
  (SHOW INDEX 实测有 uk_project_unit, doc 老版本误标 )
- manager_projects.md §9.5: 新增索引分析章节, 记录 8 个关键查询
  EXPLAIN 命中 + 已加/评估后未加的索引决策

DB 实际改动 (已落库, 无 .sql 文件):
- ADD INDEX idx_assign_exec_user_project (exec_user_id, project_id)
  on biz_project_assign — 服务机机构 EXISTS+IN covering 优化
- DROP 临时测试的 idx_org_type_id — 优化器未采纳, 33 行数据量不值
This commit is contained in:
郭庆泰
2026-08-20 22:39:34 +08:00
parent 2b69ef1c92
commit be1c973366
2 changed files with 41 additions and 3 deletions
+37 -1
View File
@@ -377,7 +377,43 @@ CREATE TABLE `biz_project` (
- ✅ biz_project: uk_project_no — 项目编号唯一,合理
- ✅ biz_project_rating: uk_rating (project_id, rater_id, rater_role) — 一人评一次,合理
- biz_project_assign: **缺唯一约束 (project_id, execution_unit_id)** — 可能重复分配同一执行方
- biz_project_assign: uk_project_unit (project_id, execution_unit_id) — 同一项目同一执行方只能分配一次
### 9.5 索引分析与 EXPLAIN (2026-08-20)
数据规模: biz_project 12 行 / biz_org 33 行 / biz_project_assign 12 行 / sys_user 47 行 (测试数据)
| # | 查询 | 命中索引 | 备注 |
|---|---|---|---|
| Q1 | `selectSponsorOrgOptions` | `idx_org_type_status` (2 列) | 优化器在小数据量倾向小索引, `org_name LIKE` 走 Using where 二次过滤, `ORDER BY org_id DESC` 走 filesort |
| Q2 | `selectExecutorOrgOptions` | `idx_org_type_status` + `PRIMARY` sys_user | 同上, `parent_user_id IS NULL` 走 Using where |
| Q3 | `selectList` 主项目 + `sponsor_admin_user_id IN` | `idx_sponsor_admin_user_id` range + Using index | ✅ 完美 covering, 无需回表 |
| Q4 | `selectList` 主项目 + `EXISTS(exec_user_id IN)` | `idx_assign_exec_user_project` + LooseScan | ✅ 完美 covering (新加索引, 见下) |
| Q5 | `selectList` 主项目 + sponsor IN + 多 LEFT JOIN + GROUP_CONCAT 子查询 | `idx_project_form` Backward scan + 3 个 LEFT JOIN 全 eq_ref/Using index + `uk_project_unit` GROUP_CONCAT | ✅ 全部走索引 |
| Q6 | `selectList` 主项目 + 单独 exec EXISTS (无其他过滤) | `idx_assign_exec_user_project` range + Start temporary | ⚠️ 优化器会 material 子查询, 数据量小可接受 |
| Q7 | selectList 同时 sponsor IN + exec EXISTS | `idx_project_form` 主表 + `idx_assign_exec_user_project` FirstMatch 子查询 | ✅ 组合最优, semi-join |
| Q8 | selectList 仅 projectForm + isFinished | `index_merge(intersect(idx_project_form, idx_is_finished))` | ✅ MySQL 8 自动交集 |
**已加索引** (2026-08-20):
```sql
ALTER TABLE biz_project_assign ADD INDEX idx_assign_exec_user_project (exec_user_id, project_id);
```
理由: Q4/Q6/Q7 (服务机机构 EXISTS+IN) 原命中 `idx_assign_exec_user(exec_user_id)` 单列索引,
需回表读 project_id; 新加复合索引直接 covering, EXPLAIN 显示 `Using index` + `LooseScan`/`range`, 无需回表。
**评估后未加的索引** (记录备查):
- `biz_org (org_type, org_id, org_name)` — 用于消除 options 查询的 filesort。
评估: 强制 hint 后确实变 `Backward index scan` 无 filesort,
但优化器在 33 行数据下仍选 `idx_org_type_status` (2 列更小, 估算成本更低)。
决策: 数据量小不需要, **DROP**, 等生产数据增长到 1k+ 行再 EVAL。
- `biz_org (org_type, status, is_published)` 等 — 无明显 QPS 压力场景, 不加。
**过时注释修正** (2026-08-20):
- `BizOrgMapper.xml` 旧注释写 "走 idx_org_user_type_name 索引", EXPLAIN 实际命中 `idx_org_type_status`, 已修正为实测命中索引 + Using where 描述
### 9.4 业务闭环检查