当前方案的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def move_folder(folder_id, new_parent_id): folder = Folder.objects.get(id=folder_id) new_parent = Folder.objects.get(id=new_parent_id) new_path = f"{new_parent.path}{folder.name}/" def update_children_paths(parent_folder, old_base, new_base): children = Folder.objects(parent=parent_folder) for child in children: child.path = child.path.replace(old_base, new_base, 1) child.save() update_children_paths(child, old_base, new_base) old_path = folder.path folder.parent = new_parent folder.path = new_path folder.save() update_children_paths(folder, old_path, new_path)
|
性能瓶颈点:
- 递归查询和更新:对于大型子树,会产生大量数据库操作
- 逐个文档更新:
save()
操作是逐个执行的,不是批量操作
- 路径重建:每个子文件夹都需要重新构建完整路径
优化方案
方案1:延迟路径更新 + 动态路径计算
核心思想:不立即更新子文件夹路径,而是在查询时动态计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Folder(Document): path_modified = BooleanField(default=False)
def move_folder(folder_id, new_parent_id): folder = Folder.objects.get(id=folder_id) new_parent = Folder.objects.get(id=new_parent_id) folder.parent = new_parent folder.path_modified = True folder.save() Folder.objects(parent=folder).update(set__path_modified=True)
def get_path(folder_id): folder = Folder.objects.get(id=folder_id) if not folder.path_modified: return folder.path.split('/') path = [] current = folder while current: path.insert(0, current.name) current = current.parent return path
|
优点:
缺点:
- 读取路径时可能需要额外计算
- 需要后台任务定期修复路径
方案2:批量更新优化
如果必须保持路径实时准确,可以使用更高效的批量更新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def move_folder(folder_id, new_parent_id): folder = Folder.objects.get(id=folder_id) new_parent = Folder.objects.get(id=new_parent_id) old_path = folder.path new_path = f"{new_parent.path}{folder.name}/" folder.parent = new_parent folder.path = new_path folder.save() Folder.objects(path__startswith=old_path).update( path=lambda f: new_path + f.path[len(old_path):] )
|
优点:
- 使用单个批量更新操作
- 利用了MongoDB的更新操作符
缺点:
- 仍然需要更新大量文档
- 对于非常大的子树可能有性能问题
方案3:混合方案(推荐)
结合前两种方案的优点:
- 移动时只更新直接父引用
- 添加一个后台任务异步更新路径
- 查询时优先使用存储的路径,如果标记为已修改则动态计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def move_folder(folder_id, new_parent_id): Folder.objects(id=folder_id).update( set__parent=new_parent_id, set__path_modified=True ) start_background_path_update(folder_id)
def update_paths_in_background(folder_id): folder = Folder.objects.get(id=folder_id) new_parent = folder.parent new_path = f"{new_parent.path}{folder.name}/" if new_parent else f"/{folder.name}/" Folder.objects(path__startswith=folder.old_path).update( path=lambda f: new_path + f.path[len(folder.old_path):], set__path_modified=False )
|
性能对比
方案 |
移动操作复杂度 |
读取复杂度 |
数据一致性 |
实现复杂度 |
原始方案 |
O(n) - 递归更新所有子项 |
O(1) |
强一致 |
简单 |
延迟更新 |
O(1) |
O(d) - d为目录深度 |
最终一致 |
中等 |
批量更新 |
O(n) - 但单次批量操作 |
O(1) |
强一致 |
中等 |
混合方案 |
O(1) |
O(1)或O(d) |
最终一致 |
复杂 |
建议
- 对于小型系统:使用原始方案足够,简单可靠
- 对于中型系统:使用批量更新方案
- 对于大型系统:采用混合方案,结合异步更新和动态计算
您可以根据实际业务需求和数据规模选择合适的方案。如果移动大型文件夹不是高频操作,原始方案可能已经足够;如果是高频操作且性能关键,则推荐混合方案。