事务中的数据争用

本页面介绍了事务数据争用、可序列化和隔离。如需查看事务代码示例,请参阅事务和批量写入

事务和数据争用

如需保证事务成功,需要确保事务以外的操作不会修改事务读取操作所检索的文档。如果其他操作尝试更改其中一个文档,则该操作将与事务进行数据争用。

数据争用
数据争用指两个或更多操作展开竞争,试图控制同一个文档。例如,一个事务可能要求文档保持一致,而另一个并发操作尝试更新该文档的字段值。

Firestore 会让其中一个操作延迟或失败,来解决数据争用问题。Firestore 客户端库会自动重试由于数据争用失败的事务。有限次数的重试之后,事务操作会失败并返回错误消息:

ABORTED: Too much contention on these documents. Please try again.

在决定让哪个操作失败或延迟时,行为取决于客户端库的类型。

  • 移动/Web SDK 使用乐观并发控制。

  • 服务器客户端库使用悲观并发控制。

移动/Web SDK 中的数据争用

移动/Web SDK(Apple 平台、Android、Web、C++)使用乐观并发控制来解决数据争用。

乐观并发控制
乐观并发控制假设不太可能发生数据争用或持有数据库锁效率不高。乐观事务不使用数据库锁来阻止其他操作更改数据。

移动 SDK/Web SDK 使用乐观并发控制,因为它们可能在高延迟和不可靠的网络连接环境下运行。在高延迟环境中锁定文档会导致过多的数据争用失败。

在移动/Web SDK 中,事务会跟踪您在事务中读取的所有文档。仅当在事务执行期间没有任何文档发生更改时,事务才会完成其写入操作。如果任何文档确实发生更改,则事务处理程序将重试该事务。如果几次重试后事务仍未得到干净的结果,事务会由于数据争用而失败。

服务器客户端库中的数据争用

服务器客户端库(C#、Go、Java、Node.js、PHP、Python、Ruby)使用悲观并发控制来解决数据争用。

悲观并发控制
悲观并发控制假设很有可能发生数据争用。悲剧事务使用数据库锁来防止其他操作修改数据。

服务器客户端库使用悲观并发控制,因为它们假定延迟低且与数据库的连接可靠。

在服务器客户端库中,事务会锁定其读取的文档。事务对文档的锁定会阻止其他事务、批量写入和非事务性写入更改该文档。在提交时,事务会释放其文档锁。如果事物因任何原因超时或失败,也会释放文档锁。

当事务锁定文档时,其他写入操作必须等待事务释放锁。事务按时间顺序获取锁。

可序列化隔离

事务之间的数据争用与数据库隔离级别密切相关。数据库的隔离级别描述了系统处理并发操作之间的冲突的表现。冲突来自以下数据库要求:

  • 事务要求准确、一致的数据。
  • 为高效利用资源,数据库会并发地执行操作。

在隔离级别较低的系统中,事务中的读取操作可能会从并发操作中未提交的更改中读取不准确的数据。

可序列化隔离定义了最高隔离级别。可序列化隔离意味着:

  • 您可以假设数据库按顺序执行事务。
  • 事务不会受并发操作中未提交的更改影响。

即使数据库并行执行多个事务,上述保证也必须成立。数据库必须实现并发控制,以解决可能破坏此保证的冲突。

Firestore 可确保事务的可序列化隔离。Firestore 中的事务按提交时序列化和隔离。

按提交时间的可序列化隔离

Firestore 为每个事务分配一个提交时间,表示单个时间点。当 Firestore 向数据库提交事务的更改时,您可以假定事务中的所有读写操作恰好都在提交时间发生。

事务的实际执行需要一些时间。事务的执行发生在提交时间之前,多个操作的执行可能会重叠。Firestore 会保持可序列化隔离,并保证:

  • Firestore 按提交时间按顺序提交事务。
  • Firestore 会将事务与提交时间靠后的并发操作隔离开来,

对于并发操作之间的数据争用,Firestore 会使用乐观和悲观并发控制来解决争用。

事务中的隔离

事务隔离也适用于事务中的写入操作。事务内的查询和读取看不到该事务内先前写入的结果。即使在事务中修改或删除文档,该事务中的所有文档读取都会返回提交时(事务的写入操作之前)的文档版本。如果文档不存在,读取操作不会返回任何内容。

数据争用问题

如需详细了解数据争用及其解决方法,请参阅“问题排查”页面