事务可序列化和隔离

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

事务和数据争用

要使事务成功,其读取操作检索的文档必须保持不被事务以外的操作修改。如果其他操作尝试更改其中一个文档,则该操作将进入与事务进行数据争用的状态。

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

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

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

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

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

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

移动/Web SDK 中的数据争用

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

乐观并发控制
基于假设:数据争用的可能性不高,或者保持数据库锁效率不高。乐观事务不会使用数据库锁来防止其他操作更改数据。

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

在移动/Web SDK 中,事务会跟踪您在事务中读取的所有文档。只有事务执行期间没有任何文档发生更改,事务才会完成其写入操作。如果有任何文档发生更改,则事务处理程序会重试事务。如果事务在多次重试后无法得到干净的结果,则事务可能会因数据争用而失败。

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

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

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

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

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

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

可序列化隔离

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

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

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

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

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

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

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

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

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

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

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

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

事务中的隔离

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