Swift Firestore:如何仅更新玩家参与过的游戏子集合文档
优化Firestore玩家昵称批量更新方案
问题背景
当玩家修改游戏昵称(gamer tag)时,需要更新其参与过的所有游戏中对应的玩家信息。现有Firestore数据结构如下:
(collection) games -> (document) randomId -> (collection) players -> (document) playerId
当前实现是遍历所有游戏文档,逐个检查是否存在该玩家,存在则更新昵称,这种嵌套遍历的方式成本极高,需要一种无需遍历全部游戏的更优方案。
当前实现代码
// 获取所有游戏文档 self.db.collection("games") .getDocuments() { (querySnapshot, err) in // 遍历所有游戏 for document in querySnapshot!.documents { // 检查当前游戏中是否存在该玩家 self.db.collection("games") .document(document.documentId) .collection("players") .document(playerId).getDocument { (snapshot, error) in guard let _snapshot = snapshot else {return} // 如果玩家存在,更新昵称 if _snapshot.exists { self.db.collection("games") .document(document.documentId) .collection("players") .document(playerId) .setData([ "gamerTag": gamerTag, ], merge: true){ err in if let err = err { print("Error updating gamer tag: \(err)") } } } } } }
优化方案
方案1:添加反向关联集合
新增一个playerGames集合,每个玩家对应一个文档,存储该玩家参与过的所有游戏ID列表,数据结构如下:
(collection) playerGames -> (document) playerId -> "gameIds": ["gameId1", "gameId2", ...]
修改昵称时只需:
- 从
playerGames/{playerId}获取该玩家参与的所有游戏ID - 用批量更新操作,直接修改对应游戏下的玩家昵称
代码示例:
// 获取玩家参与的所有游戏ID self.db.collection("playerGames").document(playerId).getDocument { snapshot, error in guard let data = snapshot?.data(), let gameIds = data["gameIds"] as? [String] else { return } // 初始化批量更新任务 let batch = self.db.batch() for gameId in gameIds { let playerRef = self.db.collection("games").document(gameId).collection("players").document(playerId) batch.setData(["gamerTag": gamerTag], merge: true, forDocument: playerRef) } // 提交批量更新 batch.commit { err in if let err = err { print("批量更新失败: \(err)") } else { print("所有游戏中的昵称已更新完成") } } }
注意:需要在玩家加入游戏时,用arrayUnion操作同步更新playerGames/{playerId}的gameIds数组;玩家离开游戏时用arrayRemove维护该列表,保证数据一致性。
方案2:使用Firestore集合组查询
利用Firestore的集合组查询,直接定位所有players子集合中ID为playerId的文档,无需遍历全部游戏。
代码示例:
// 集合组查询所有players子集合中ID为playerId的文档 self.db.collectionGroup("players").whereField(FieldPath.documentID(), isEqualTo: playerId).getDocuments { querySnapshot, error in guard let documents = querySnapshot?.documents else { return } let batch = self.db.batch() for doc in documents { batch.setData(["gamerTag": gamerTag], merge: true, forDocument: doc.reference) } batch.commit { err in if let err = err { print("批量更新失败: \(err)") } else { print("所有游戏中的昵称已更新完成") } } }
注意:需要提前在Firestore控制台为players集合组创建索引——进入控制台→索引→集合组索引→添加索引,选择players集合,字段无需指定(按文档ID查询),等待索引启用后再执行查询。
方案对比
- 反向关联集合:查询速度快,无需依赖额外索引,但需要额外维护玩家的游戏列表,适合游戏数量较多的场景。
- 集合组查询:无需额外维护数据结构,但需要创建集合组索引,查询性能取决于Firestore的索引优化,适合中小规模游戏数量场景。
内容的提问来源于stack exchange,提问作者AvidKain
相关产品推荐
相关产品推荐

