交易
事务是作为单个逻辑工作单元执行的一系列操作。 它展示了数据库事务的常见 ACID(原子性、 一致性、 隔离性、 持久性)属性:
- 原子性:事务必须是原子工作单元。 换而言之,要么执行其所有数据修改,要么一个数据修改也不执行。
- 一致性:完成后,事务必须使所有数据处于一致状态。 事务结束时,所有内部数据结构必须都正确。
- 隔离:并发事务所做的修改必须与任何其他并发事务所做的修改隔离。 用于 ITransaction 中某个操作的隔离级别由执行该操作的 IReliableState 确定。
- 持久性:事务完成后,其效果将永久放置在系统中。 即使系统发生故障,修改也会保留。
隔离级别
隔离级别定义必须从其他事务所作修改中隔离事务的程度。 Reliable Collections 支持两种隔离级别:
可重复读取:指定语句无法读取已修改但尚未由其他事务提交的数据,并且其他事务无法修改当前事务在当前事务完成之前已读取的数据。
- 读取可重复事务的优点:
- 比快照事务占用的内存更少
- 确保在特定时间进行多次读取时返回的值一致。
- 读取可重复事务的注意事项:
当数据正在被读取时,写入操作会被阻止。
在数据正在进行写入时,读取也会被阻止。
- 读取可重复事务的优点:
快照:指定事务中任何语句读取的数据都是事务开始时便存在的数据的事务上一致的版本。 事务只能识别在事务的首次读取之前提交的数据修改。 在当前事务中执行的语句看不到在当前事务开始以后由其他事务所做的数据修改。
其效果就好像事务中的语句获得了已提交数据的快照,因为该数据在事务开始时就存在。 快照跨 Reliable Collections 一致。
- 快照事务的优点:
- 即使某个值在未提交事务中已被读取,也允许写入
- 快照事务的注意事项:
- 长时间运行的快照事务可能会导致服务占用大量内存,因为将更多值移到内存中以保持快照隔离
- 不同的事务可以返回读取数据的不同值,具体取决于拍摄快照时间
- 快照事务的优点:
Reliable Collections 会在事务创建时根据副本的操作和角色,为指定读取操作自动选择要使用的隔离级别。 下表描述了用于 Reliable Dictionary 和 Reliable Queue 操作的默认隔离级别。
操作\角色 | 主要 | 次要 |
---|---|---|
单个实体读取 | 可重复的读取 | 快照 |
枚举、计数 | 快照 | 快照 |
注释
单个实体操作的常见示例为 IReliableDictionary.TryGetValueAsync
、IReliableQueue.TryPeekAsync
。
可靠字典和可靠队列都支持“读取写入”。 换而言之,事务中的任何写入都将对属于同一事务的后续读取可见。
锁
在可靠集合中,所有事务都严格实施两个阶段的锁定:在以中止或提交操作终止事务之前,该事务不会释放所获取的锁。
可靠字典对所有单个实体操作使用行级别锁定。
Reliable Queue 权衡严格事务性 FIFO 属性的并发。
可靠队列使用操作级别锁,允许具有 TryPeekAsync
和/或 TryDequeueAsync
的事务与具有 EnqueueAsync
的事务同时进行。
请注意,为了保证 FIFO 原则的实施,如果 TryPeekAsync
或 TryDequeueAsync
曾经观察到可靠队列为空,它们也会锁定 EnqueueAsync
。
写入操作始终采用排他锁。 对于读取操作,锁定取决于几个因素:
- 使用快照隔离完成的任何读取操作都是无锁的。
- 任何可重复读取操作默认情况下均采用共享锁。
- 但是,对于任何支持可重复读取的读取操作,用户可以要求使用更新锁而非共享锁。 更新锁是一种非对称锁,用于防止当多个事务为随后可能进行的更新锁定资源时发生常见的死锁。
锁兼容性矩阵可在下表中找到:
请求\授予 | 没有 | 共享 | 更新 | 排他 |
---|---|---|---|---|
共享 | 无冲突 | 无冲突 | 冲突 | 冲突 |
更新 | 无冲突 | 无冲突 | 冲突 | 冲突 |
排他 | 无冲突 | 冲突 | 冲突 | 冲突 |
可靠集合 API 中的超时参数用于死锁检测。 例如,两个事务(T1 和 T2)正在尝试读取和更新 K1。 它们有可能发生死锁,因为它们最后都拥有共享锁。
在这种情况下,其中一个操作或两个操作都将超时。在这种情况下,更新锁可以防止这种死锁。