diff --git a/_self/manager_projects.md b/_self/manager_projects.md index d6330b8..938c7be 100644 --- a/_self/manager_projects.md +++ b/_self/manager_projects.md @@ -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 业务闭环检查 diff --git a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizOrgMapper.xml b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizOrgMapper.xml index 4d3fcb9..5b8b21b 100644 --- a/ry-api/ruoyi-business/src/main/resources/mapper/business/BizOrgMapper.xml +++ b/ry-api/ruoyi-business/src/main/resources/mapper/business/BizOrgMapper.xml @@ -104,7 +104,8 @@