Firestore如何查询不存在指定token_id的文档并避免重复创建
问题根因
你当前代码出现重复数据的核心原因有3个:
- 查询逻辑完全写反:使用
whereField("token_id", isNotEqualTo: user_token)会返回所有token_id不等于当前值的文档,只要集合里存在任意一条其他token的记录,查询结果就非空,但你没有判断查询结果里是否包含当前token,反而直接执行写入逻辑,必然会重复创建文档。 - 先查后写存在竞态风险:就算把查询条件改成
isEqualTo匹配当前token,遇到客户端重复触发、网络重试等场景时,可能出现多个请求同时查到「无对应文档」,进而先后写入多条重复数据。 - 写入时未绑定唯一约束:调用
db.collection("tokenList").document()会生成随机ID的新文档,没有和user_token做绑定,天然无法从数据库层面阻止重复数据生成。
你遇到的重复数据问题示例:
最优实现方案
不要用先查再写的逻辑,直接利用Firestore文档ID的全局唯一特性实现去重,可靠性最高、性能最好:
- 将
user_token直接作为tokenList集合下的文档ID,同一个token永远只会对应唯一文档,从数据库底层避免重复条目生成,不需要提前做存在性查询。 - 重复触发写入时只会更新同一条文档的内容,不会产生新的重复文档,完全满足业务需求。
对应实现代码:
// 初始化配置 let db = Firestore.firestore() let user_token = "dpc3GPfPzUArj...IdEac5hWYWI8" // 直接用user_token作为文档ID,天然唯一 let tokenDocRef = db.collection("tokenList").document(user_token) // 写入数据,同token多次调用只会操作同一条文档 tokenDocRef.setData([ "token_id": user_token, "subscribeTime": Timestamp() ]) { err in if let err = err { print("token记录写入失败: \(err.localizedDescription)") } else { print("token记录已生效,无重复数据") } }
严格不覆盖已有数据的实现
如果业务要求「文档已存在时完全不做任何写入修改」,可以用事务保证原子性,避免竞态:
let tokenDocRef = db.collection("tokenList").document(user_token) db.runTransaction({ (transaction, errorPointer) -> Any? in let snapshot: DocumentSnapshot do { try snapshot = transaction.getDocument(tokenDocRef) } catch let err as NSError { errorPointer?.pointee = err return nil } // 仅当文档不存在时执行创建 if !snapshot.exists { transaction.setData([ "token_id": user_token, "subscribeTime": Timestamp() ], forDocument: tokenDocRef) } return nil }) { _, err in if let err = err { print("事务执行失败: \(err.localizedDescription)") } else { print("操作完成,无重复数据") } }
注意:如果一定要用
token_id字段做匹配而不是用文档ID,必须配合Firestore安全规则做唯一校验兜底,否则仅靠客户端的查询+写入逻辑永远无法100%避免并发导致的重复数据。
内容的提问来源于stack exchange,提问作者Joe
相关产品推荐
相关产品推荐

