Firestore C++中如何安全获取Increment更新后的整数数据?
解决Firestore原子递增后读取最新值的一致性问题
你遇到的核心问题是:Increment的原子递增没问题,但后续独立的Get操作无法避免被其他客户端的写操作插队,导致读到非自己更新后的脏数据。下面提供三种可行的解决方案,按可靠性和适用场景排序:
方案1:使用Firestore事务(最可靠)
事务能保证读取-修改-读取的全流程原子性,执行期间其他对该文档的写操作会被阻塞,直到事务提交完成,完全杜绝中间被干扰的可能。
C++ SDK实现示例:
// 假设你有成员变量存储最新计数 int latest_count = 0; db->RunTransaction([this, ref_name](firebase::firestore::Transaction* transaction) -> firebase::firestore::Error { // 1. 在事务内读取当前文档状态 auto get_future = transaction->Get(db->Collection("collection_name").Document(ref_name)); // 事务回调是同步执行的,需等待读取完成 while (get_future.status() == firebase::kFutureStatusPending) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } if (get_future.error() != 0) { return get_future.error(); } const auto& doc = *get_future.result(); // 2. 执行原子递增(用Update而非Set,避免覆盖整个文档) firebase::firestore::MapFieldValue updates; updates[ref_name] = firebase::firestore::FieldValue::Increment(1); auto update_future = transaction->Update(db->Collection("collection_name").Document(ref_name), updates); while (update_future.status() == firebase::kFutureStatusPending) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } if (update_future.error() != 0) { return update_future.error(); } // 3. 在事务内读取更新后的文档(事务内的读取会保证看到自己的修改) auto updated_get_future = transaction->Get(db->Collection("collection_name").Document(ref_name)); while (updated_get_future.status() == firebase::kFutureStatusPending) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } if (updated_get_future.error() != 0) { return updated_get_future.error(); } const auto& updated_doc = *updated_get_future.result(); latest_count = updated_doc.Get(ref_name).integer_value(); return firebase::firestore::Error::kErrorOk; }).OnCompletion([this](const firebase::Future<void>& future) { if (future.error() != 0) { std::cerr << "事务执行失败: " << future.error_message() << std::endl; // 可根据需求添加重试逻辑 } else { std::cout << "更新后的计数: " << latest_count << std::endl; // 这里触发后续业务逻辑 } });
注意事项:
- 事务回调是同步执行的,必须在内部同步等待Future完成,不能用异步
OnCompletion - 优先用
Update代替Set,除非你明确需要覆盖整个文档(可配合SetOptions::Merge) - 事务失败时Firestore会自动重试(默认次数有限),所以回调内的逻辑要保证幂等性
方案2:本地计算(最简单,仅限特定场景)
如果该字段只会被Increment操作修改(没有其他直接Set该字段的逻辑),你可以直接在本地计算更新后的值,无需再次请求服务器:
doc_ref.Get().OnCompletion([this, ref_name](const firebase::Future<firebase::firestore::DocumentSnapshot>& future) { if (future.error() == 0) { const auto& doc = *future.result(); int current_count = doc.Get(ref_name).integer_value(); int expected_new_count = current_count + 1; db->Collection("collection_name").Document(ref_name).Update({ ref_name, firebase::firestore::FieldValue::Increment(1) }) .OnCompletion([this, expected_new_count](const firebase::Future<void>& void_future) { if (void_future.error() == 0) { // 直接使用本地计算值,Increment保证原子递增1 latest_count = expected_new_count; } }); } });
这个方案的局限性很明显:如果存在其他非Increment的写操作修改该字段,本地计算的值会和服务器实际值不一致。
方案3:实时快照监听(适合持续关注文档变化的场景)
在发起递增操作前注册文档监听,通过回调获取最新快照,并过滤出自己的更新:
// 标记是否在等待自己的更新 bool waiting_for_self_update = false; // 保存监听注册,方便后续取消 firebase::firestore::ListenerRegistration listener; // 注册快照监听 listener = db->Collection("collection_name").Document(ref_name) .AddSnapshotListener([this, ref_name](const firebase::firestore::DocumentSnapshot* snapshot, const firebase::firestore::Error* error) { if (error != nullptr && *error != firebase::firestore::Error::kErrorOk) { return; } if (snapshot != nullptr && snapshot->exists() && waiting_for_self_update) { latest_count = snapshot->Get(ref_name).integer_value(); waiting_for_self_update = false; // 仅需一次更新的话,取消监听 listener.Remove(); } }); // 发起递增操作 waiting_for_self_update = true; db->Collection("collection_name").Document(ref_name).Update({ ref_name, firebase::firestore::FieldValue::Increment(1) }) .OnCompletion([this](const firebase::Future<void>& void_future) { if (void_future.error() != 0) { waiting_for_self_update = false; std::cerr << "递增操作失败: " << void_future.error_message() << std::endl; } });
这种方式能实时获取文档状态,但需要额外逻辑区分更新来源(比如通过时间戳或本地标记)。
内容的提问来源于stack exchange,提问作者Josh Hardman
相关产品推荐
相关产品推荐

