def allocate_next_id(namespace: str, area: str, category: str) -> str:
"""
原子分配下一个可用 ID
使用 IMMEDIATE 事务模式 + UNIQUE 约束确保并发安全
"""
max_retries = 3
for attempt in range(max_retries):
try:
# ⚠️ 关键:使用 IMMEDIATE 模式,事务开始时立即获取写锁
# 这会阻塞其他写事务,避免空 Category 的竞态条件
cursor.execute("BEGIN IMMEDIATE")
# 1. 查询当前 Category 下最大 ID
# 同时查询 id_reservations 表(可能有预占但未使用的 ID)
cursor.execute("""
SELECT MAX(id_num) FROM (
SELECT CAST(SUBSTR(jd_number, -4) AS INTEGER) as id_num
FROM resources
WHERE namespace = ? AND jd_number LIKE ?
UNION ALL
SELECT CAST(SUBSTR(jd_number, -4) AS INTEGER) as id_num
FROM id_reservations
WHERE namespace = ? AND jd_number LIKE ?
)
""", (namespace, f"{area}.{category}.%",
namespace, f"{area}.{category}.%"))
row = cursor.fetchone()
max_id = (row[0] if row else None) or 0
next_id = max_id + 1
if next_id > 9999:
cursor.execute("ROLLBACK")
raise OverflowError(f"Category {area}.{category} ID exhausted (max 9999)")
# 2. 预占 ID(UNIQUE 约束是最后一道防线)
new_jd = f"{area}.{category}.{next_id:04d}"
cursor.execute("""
INSERT INTO id_reservations (jd_number, namespace, reserved_at)
VALUES (?, ?, datetime('now'))
""", (new_jd, namespace))
cursor.execute("COMMIT")
return new_jd
except sqlite3.IntegrityError:
# UNIQUE 约束冲突,说明有并发分配,重试
cursor.execute("ROLLBACK")
if attempt == max_retries - 1:
raise AllocationError(f"Failed to allocate ID after {max_retries} retries")
continue # 重试
def release_reservation(jd_number: str):
"""创建失败时释放预占的 ID"""
cursor.execute("DELETE FROM id_reservations WHERE jd_number = ?", (jd_number,))
**id_reservations 表结构**:
CREATE TABLE id_reservations (
jd_number TEXT PRIMARY KEY, -- ⚠️ UNIQUE 约束,防止重复分配
namespace TEXT NOT NULL,
reserved_at TEXT NOT NULL,
INDEX idx_namespace_jd (namespace, jd_number)
);
-- 定期清理过期预占(超过 1 小时未使用)
DELETE FROM id_reservations
WHERE reserved_at < datetime('now', '-1 hour');
**并发控制机制**:
| 层级 | 机制 | 作用 |
|------|------|------|
| 1 |
BEGIN IMMEDIATE | 事务级写锁,阻塞其他写事务 || 2 | 查询含
id_reservations | 感知其他进程的预占 || 3 |
jd_number PRIMARY KEY | UNIQUE 约束,最后防线 || 4 | 重试机制 | 冲突时自动重试 |
**批量导入优化**:
```bash
# 批量模式:一次性预占 N 个 ID,减少锁竞争
syn import --batch /path/to/files/ --count 100
# 内部实现:单次事务分配 100 个连续 ID
**ID 耗尽处理**:
- 每个 Category 最多 9999 个资源
- 接近上限(>9000)时 CLI/TUI 显示警告
- 建议用户创建新 Category 或清理归档资源
### 3.2 短 ID 支持
为降低 CLI 输入摩擦,支持 Git 风格的短 ID:
| 输入方式 | 示例 | 匹配规则 |
|----------|------|----------|
| 完整 ID |
10.001.0015 | 精确匹配 || 短 ID |
10.1.15 | 自动补零 → 10.001.0015 || 最短 ID |
15 | 当前 Namespace 内唯一匹配 || 标题模糊 |
设计文档 | Fuzzy Finder 模糊搜索 |
syn show 15 # 当前 Namespace 内唯一,直接匹配
syn show 10.1.15 # 自动补零
syn show 设计 # Fuzzy Finder
**歧义处理**:当短 ID 匹配到多个结果时:
- **CLI**:报错并列出候选项,用户需输入更精确的 ID
- **TUI**:弹窗让用户选择
$ syn show 15
Error: Ambiguous ID '15'. Did you mean:
[1] 10.001.0015 - 完成项目设计 (task)
[2] 20.001.0015 - 会议纪要 (meeting)
Use full ID or choose: syn show 10.001.0015
**Shell 自动补全**:CLI 必须实现 Tab completion(支持 Bash/Zsh/Fish)。
### 3.3 目录结构(双层分桶)