0%

当前方案的问题

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)

性能瓶颈点:

  1. 递归查询和更新:对于大型子树,会产生大量数据库操作
  2. 逐个文档更新save() 操作是逐个执行的,不是批量操作
  3. 路径重建:每个子文件夹都需要重新构建完整路径

优化方案

方案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

优点

  • 移动操作变为O(1)复杂度
  • 避免了大规模更新

缺点

  • 读取路径时可能需要额外计算
  • 需要后台任务定期修复路径

方案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}/"

# 1. 更新移动的文件夹本身
folder.parent = new_parent
folder.path = new_path
folder.save()

# 2. 批量更新所有子文件夹路径
# 使用MongoDB的$regex查找所有以old_path开头的路径
Folder.objects(path__startswith=old_path).update(
path=lambda f: new_path + f.path[len(old_path):]
)

优点

  • 使用单个批量更新操作
  • 利用了MongoDB的更新操作符

缺点

  • 仍然需要更新大量文档
  • 对于非常大的子树可能有性能问题

方案3:混合方案(推荐)

结合前两种方案的优点:

  1. 移动时只更新直接父引用
  2. 添加一个后台任务异步更新路径
  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) 最终一致 复杂

建议

  1. 对于小型系统:使用原始方案足够,简单可靠
  2. 对于中型系统:使用批量更新方案
  3. 对于大型系统:采用混合方案,结合异步更新和动态计算

您可以根据实际业务需求和数据规模选择合适的方案。如果移动大型文件夹不是高频操作,原始方案可能已经足够;如果是高频操作且性能关键,则推荐混合方案。