事务基础-本地事务

672 阅读5分钟

事务简介

事务的目标是在多个事务访问对象以及服务器面临故障的情况下,保证由服务器管理的对象始终保持一致的状态。事务是由客户端定义的针对服务器对象的一组操作,它们组成一个不可分割的单元,由服务器执行。服务器必须保证或者整个事务被执行并将执行结果记录到持久化存储中,或者在出现故障时,能完全回滚这些操作。

本地事务

计算机系统多是本地事务控制,基于 ACID 特性实现。

  • 原子性(Atomicity):所有操作要么成功,要么都失败。
  • 一致性(Consistency):事务执行完,服务器数据从一个一致性状态转化到另外一个一致性状态。
  • 隔离性(Isolation):多个事务之间是并发的。隔离代表两个事务之间相互隔离,互不影响。
  • 持久性(Durability):事务完成后,数据持久化到磁盘中。

为了支持故障原子性和持久性要求,对象必须是可恢复的。当服务完成一个事务时,事务中所有对象必须改变且记录到持久化存储中。当服务器进行崩溃时,所有已完成的事务更新必须永久性保留在持久存储中。当服务被新的进程替代时,可以利用这些持久信息恢复对象。如果用数据库举例的话,事务中的对象就是数据库表中的数据。持久化就是将数据改变持久化到磁盘中。

Coordinator {
    开始一个事务,并返回事务的唯一TID
    openTransaction(defination) -> trans;
    结束事务:如果返回值为commit,表示事务提交成功;否则返回abort,表示事务被放弃
    closeTransaction(trans) -> (commit, abort);
    放弃事务
    abortTransaction(trans);
}

每个事务都由协调者创建和管理,协调者就是上面定义的 Coordinator 接口。协调者为每个事务创建一个唯一的事务 ID(TID)。客户传入事务定义 defination 调用 openTransaction 引入一个新的事务,事务结束时,客户调用closeTransaction表示事务结束,如果事务完成时错误则 abort 进行回滚,否则 commit 提交事务。

并发控制

事务并发中有两个著名的问题-更新丢失不一致读取问题。

更新丢失   现在有三个银行账户 A、B、C 余额分别是100200、200、300。事务T控制A转账20。事务 T 控制 A 转账20到 B,事务 U 控制从 C 转账20B。事务UT完成时B账户的余额时220到 B。事务 U 和 T 完成时 B 账户的余额时220,并不是预期的240$。

不一致读取    还以上面银行例子说明,事务 T 控制 A 转账100B,事务U读取所有账户的总余额,最后读取到的总余额时500到 B,事务 U 读取所有账户的总余额,最后读取到的总余额时500而不是预期的600$。

串行等价性    如果并发事务交错执行的效果等同于本地事务一次执行的效果相同,则称这样的操作是串行等价的。如果用串行等价性去判断并发执行是否正确,则可以防止更新丢失和不一致检索的问题。串行等价性可以作为并发控制协议的标准,并发控制协议就是将访问对象的事务并发串行化。目前常用的并发控制协议有3种:

  • 悲观锁:在一个事务访问一个对象时,就为这个对象上一把锁,在操作完成前,其他事务对此对象的访问就会被阻塞住,操作完成后就将锁释放。可以类比Java中synchronized。
  • 乐观锁:在一个事务访问一个对象,如果这个对象已经标识锁定,则放弃本次事务并重新启动该事务,直到获取这个对象的锁。
  • 时间戳比较:服务器对一个对象每次成功的读写都会记录一个时间戳,当一个事务读写这个对象发现当前的时间戳小于上一次的时间戳,则会放弃本次事务。如果配置重试,可以在一段时间后进行重试。

事务恢复隔离机制

如果多个事务之间没有使用并发控制协议标准,则有可能出现一下几种情况:

  • 更新丢失:一个事务中的写操作覆盖了另一个事务事务的写操作,导致数据丢失。
  • 脏读:一个事务读取到另一个事务未提交的数据,另一个事务又以失败而告终。
  • 虚读:事务T1读取对象的数据后,事务T2中途修改此对象的数据并提交,事务T1再次读取此对象到不同的数据。
  • 幻读:事务T1读取对象列表后,事务T2中途插入或者删除其中一个对象,事务T1再次读取对象后,前后列表不一致。

为了应对上述问题,标准 SQL 协议定义了四种隔离机制:

  • 读未提交(Read Uncommited):可以读取到未提交的数据,容易产生脏读。
  • 读已提交(Read Commited):只能读取到以提交的数据,避免了脏读,但是无法避免虚读。
  • 可重复度(Read Reportable):一个事务中重复读取数据都是相同的,避免了虚读,但是无法避免幻读。
  • 序列化(Serialization):所有的事务都是串行化执行的,理论上可以屏蔽一些读写问题,是最严格的机制。

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把隔离级别设为 Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致虚度、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。