Firestore+Unity集成:手动修改数据后读取次数异常飙升求助
问题背景
在Unity集成Firestore SDK时出现异常:
- 数据结构:
users主集合下每个用户文档包含progress子集合存储玩家进度 - 权限配置:默认仅允许写入,测试时临时开放读取权限
- 异常现象:预期仅2次的文档读取操作,实际产生150-600次读取量;手动在Firestore后台修改数据后,读取次数随机且远超预期,但正常代码读写操作无此问题
原读取代码:
public static async Task<Dictionary<string, object>> GetDocSnapshotAsync(string collection, string doc) { var result = new Dictionary<string, object>(); var getCompleted = false; var documentReference = FirebaseFirestore.DefaultInstance.Collection(collection).Document(doc); documentReference.GetSnapshotAsync().ContinueWithOnMainThread(task => { if (task.Result.Exists) { result = task.Result.ToDictionary(); } getCompleted = true; }); while (!getCompleted) { await Task.Delay(10); } return result; } public static async Task<Dictionary<string, object>> GetSubDocSnapshotAsync(string collection, string doc, string subCollection, string subDoc) { var result = new Dictionary<string, object>(); var getCompleted = false; var documentReference = FirebaseFirestore.DefaultInstance.Collection(collection).Document(doc).Collection(subCollection).Document(subDoc); documentReference.GetSnapshotAsync().ContinueWithOnMainThread(task => { if (task.Result.Exists) { result = task.Result.ToDictionary(); } getCompleted = true; }); while (!getCompleted) { await Task.Delay(10); } return result; }
可能原因
异步代码实现缺陷
原代码使用while轮询getCompleted变量等待任务完成,存在线程安全隐患:getCompleted未标记为volatile,多线程环境下可能出现缓存一致性问题,导致循环无法及时退出,甚至重复触发快照请求。本地缓存自动同步触发额外读取
手动修改云端数据后,Firestore SDK会自动同步本地缓存与云端数据。如果代码中存在未取消的实时监听(如Snapshots.Subscribe),或SDK在权限开放后批量拉取之前因权限不足缓存的过期数据,会产生大量额外读取请求。代码被意外重复调用
若读取方法被放在Unity的Update/FixedUpdate等帧循环中,或绑定到重复触发的事件(如UI按钮重复点击回调),当云端数据变化时会触发大量重复读取。权限规则变更后的缓存刷新
开放读取权限后,SDK可能会重新拉取之前因权限限制无法获取的文档,导致批量读取请求被计数。
解决方案
修复异步代码写法
替换轮询等待为直接await,避免线程安全问题和无效循环,同时简化代码逻辑:public static async Task<Dictionary<string, object>> GetDocSnapshotAsync(string collection, string doc) { var docRef = FirebaseFirestore.DefaultInstance.Collection(collection).Document(doc); var snapshot = await docRef.GetSnapshotAsync(); return snapshot.Exists ? snapshot.ToDictionary() : new Dictionary<string, object>(); } public static async Task<Dictionary<string, object>> GetSubDocSnapshotAsync(string collection, string doc, string subCollection, string subDoc) { var subDocRef = FirebaseFirestore.DefaultInstance.Collection(collection).Document(doc) .Collection(subCollection).Document(subDoc); var snapshot = await subDocRef.GetSnapshotAsync(); return snapshot.Exists ? snapshot.ToDictionary() : new Dictionary<string, object>(); }排查并清理隐式监听
检查代码中是否存在Snapshots.Subscribe或类似实时监听逻辑,确保在不需要时调用Subscribe返回的IDisposable对象的Dispose()方法取消订阅,避免数据变化时自动触发快照拉取。验证代码调用频率
在读取方法中添加日志输出,确认调用次数是否符合预期:Debug.Log($"触发读取:{collection}/{doc}");排查是否在帧循环、重复事件中意外调用了读取方法。
测试禁用本地缓存
临时禁用Firestore本地缓存,验证是否是缓存同步导致的额外读取:var settings = new FirebaseFirestoreSettings { EnablePersistence = false }; FirebaseFirestore.DefaultInstance.Settings = settings;查看Firestore用量明细
登录Firestore控制台,进入「用量」页面查看读取请求的来源(客户端IP、请求类型),确认是否为游戏客户端发起的请求,排除第三方服务或误操作的可能。
内容的提问来源于stack exchange,提问作者Всеволод Косарев

