事务简介

1. 事务定义

首先wikipedia上面对事务的定义如下

Transaction processing is information processing in computer science that is
divided into individual, indivisible operations called transactions.
Each transaction must succeed or fail as a complete unit; 
it can never be only partially complete.

翻译过来就是:事务处理是指在计算机科学中被划分为独立不可再分的操作,每个事务必须作为一个整体单元要么执行成功或者失败,不允许部分执行完成。

事务是广泛存在于计算机科学的方方面面,在某些领域如数据库中事务的支持是不可缺少的部分,最常用的例子就是A账户向B账户转账,其中涉及到两个操作,从A账户中减去转账金额,和将B账户存入转账金额,这两个操作应该是一起成功或者一起失败,如果只有一个成功了另一个失败了则会导致转账前后数据不一致,为了保证这种涉及到多个操作能够原子性的执行,就需要事务的支持。

2. 事务

数据库事务需要满足的ACID特性

  • A:Atomic,原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
  • C:Consistent,一致性,事务完成后,所有数据的状态都是一致的,即A账户只要减去了100,B账户则必定加上了100;
  • I:Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
  • D:Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储

回滚

回滚是指在无法成功执行事务的情况下通过将数据恢复到事务操作开始之前的状态来取消事务的操作,通常是使用数据副本的方式。

死锁

在某些情况下,两个事务可能会在处理过程中尝试同时访问数据的同一部分,由于操作过程中会需要申请锁住部分数据,有可能导致死锁的发生,这就类似于多线程编程中由于对同一资源的访问请求容易导致死锁的发生一样。

补偿

在没有提交机制或者使用回滚机制的系统中,可以使用补偿事务来撤销对失败事务并将系统还原回以前的状态

3. 数据库事务

事务隔离级别

在MySQL数据库中,InnoDB存储引擎提供了事务的支持,数据库事务隔离级别通常分类如下四种隔离级别

  1. 读未提交 Read Uncommitted 是隔离级别最低的一种事务级别,在这种级别下,一个事务会读到另一个事务更新后但未提交的数据,如果另一个事务回滚,那么当前事务读到的数据就是脏数据,出现脏读(dirty read)
  2. 读已提交 Read Committed 一个事务可能会遇到不可重复读的问题,不可重复读是指同一个事务内多次读取同一个数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么在第一个事务中两次读取的数据就可能不一致。
  3. 可重复读 Read Repeated 可能会出现幻读的情况,指一个事务在查询某条记录时,发现没有,但是试图更新这条不存在的纪录时,竟然能够成功,并且再次读取同一条记录,又可以读取成功。
  4. 串行 Serializable 所有事务按照顺序依次执行,并发性能会下降。
    其中MySQL默认的隔离级别为可重复读

多版本并发控制(MVCC)

MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能

  • 读锁:也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
  • 写锁:又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
  • 表锁:操作对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb),是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其他事务可读不可写,若加写锁,则其他事务增删改都不行。
  • 行级锁:操作对象是数据表中的一行。是MVCC技术用的比较多的,但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器。但行级锁对系统开销较大,处理高并发较好。

在innodb中“MVCC多版本一致性读”功能的实现是基于undo-log的。主要是为Repeatable-Read事务隔离级别做的。在此隔离级别下,事务A,B相互隔离,互相操作的数据互不可见。
innodb存储的最基本row中包含一些额外的存储信息,DATA_TRX_ID, DATA_ROLL_PTR, DB_ROW_ID, DELETE_BIT

  • 6字节的DATA_TRX_ID 标记了最新更新这条行记录的transaction id,每处理一个事务,其值自动+1
  • 7字节的DATA_ROLL_PTR 指向当前记录项的rollback segment的undo log记录,找之前版本的数据就是通过这个指针
  • 6字节的DB_ROW_ID 当innoDB自动产生聚集索引时,聚集索引包括这个DB_ROW_ID的值,否则索引中不包括DB_ROW_ID
  • DELETE BIT位用于表识该记录是否被删除,这里的删除不是真正的删除,而是标志出来的删除,真正的删除需要在commit之后

事务链表

MySQL事务在开始到提交过程中,都会被保存在一个叫做trx_sys的事务链表中

+---------+      +---------+     +----------+
|  TRX1   +----->+  TRX2   +---->+   TRX3   |
+---------+      +---------+     +----------+

事务链表中都是未提交的事务,如果事务一旦被提交则会被事务链表删除。

ReadView

ReadView是一个用于事务控制的数据结构,包含3个主要成员:ReadView{low_trx_id,up_trx_id,trx_ids},用于表识事务启动过程中哪些部分是可见的,各成员代表意思如下:

  1. low_trx_id 表示该SQL启动时,当前事务链表中最大的事务id编号,也就是最近的除自身以外的事务编号
  2. up_trx_id 表示该SQL启动时,当前事务链表中最小的事务id编号,也就是当前系统中除自身外最早但还未提交的事务
  3. trx_ids 表示所有事务链表中事务的id集合

注意,ReadView是与SQL绑定的,而并不是事务,所以即使在同一个事务中,每次SQL启动时构造的ReadView的up_trx_id和low_trx_id也都是不一样的,至于DATA_TRX_ID大于low_trx_id本身出现也只有当多个SQL并发的时候,在一个SQL构造完ReadView之后,另外一个SQL修改了数据后又进行了提交,对于这种情况,数据其实是不可见的。

最后,至于位于(up_trx_id, low_trx_id)中间的事务是否可见,这个需要根据不同的事务隔离级别来确定。对于RC的事务隔离级别来说,对于事务执行过程中,已经提交的事务的数据,对当前事务是可见的,也就是说上述图中,当前事务运行过程中,trx1~4中任意一个事务提交,对当前事务来说都是可见的;而对于RR隔离级别来说,事务启动时,已经开始的事务链表中的事务的所有修改都是不可见的,所以在RR级别下,low_trx_id基本保持与up_trx_id相同的值即可。

image

RedoLog 重做日志

redo log是InnoDB存储引擎层的日志,又称重做日志文件,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。在实例和介质失败(media failure)时,redo log文件就能派上用场,如数据库掉电,InnoDB存储引擎会使用redo log恢复到掉电前的时刻,以此来保证数据的完整性。

在一条更新语句进行执行的时候,InnoDB引擎会把更新记录写到redo log日志中,然后更新内存,此时算是语句执行完了,然后在空闲的时候或者是按照设定的更新策略将redo log中的内容更新到磁盘中,这里涉及到WAL即Write Ahead logging技术,他的关键点是先写日志,再写磁盘。

有了redo log日志,那么在数据库进行异常重启的时候,可以根据redo log日志进行恢复,也就达到了crash-safe。

redo log日志的大小是固定的,即记录满了以后就从头循环写。

UndoLog 回滚日志

保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读

binlog 二进制日志

binlog是属于MySQL Server层面的,又称为归档日志,属于逻辑日志,是以二进制的形式记录的是这个语句的原始逻辑,依靠binlog是没有crash-safe能力的

redo log和binlog区别

redo log是属于innoDB层面,binlog属于MySQL Server层面的,这样在数据库用别的存储引擎时可以达到一致性的要求。
redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录的是这个更新语句的原始逻辑
redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。

redolog和undolog的区别

undo log不是redo log的逆向过程,其实它们都算是用来恢复的日志:
1.redo log通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
2.undo用来回滚行记录到某个版本。undo log一般是逻辑日志,根据每行记录进行记录。

4. 分布式事务

分布在系统中不同实例的处理单元执行一系列的操作,并且这些操作需要确保同时成功或者同时失败,解决分布式事务的关键就是必须要有一种方法能够感知事务在任何地方所做的任何改动,提交或者回滚的决定必须产生统一的结果。

2PC 两阶段提交

  1. 第一阶段:事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.
  2. 第二阶段:事务协调器要求每个数据库提交数据,或者回滚数据。

优点:

尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于MySQL是从5.5开始支持。

缺点:

  • 单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。
  • 同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。
  • 数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能,比如在第二阶段中,假设协调者发出了事务commit的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

总的来说,XA协议比较简单,成本较低,但是其单点问题,以及不能支持高并发(由于同步阻塞)依然是其最大的弱点。

3PC 3阶段提交

主要分三个阶段

  1. 第一阶段协调者询问参与者是否可以执行事务,参与者就分析自身是否能够成功执行事务操作,可以则返回yes,否则no
  2. 第二阶段参与者收到后则开始执行事务操作,执行成功后反馈yes给协调者反之no
  3. 第三阶段协调者根据参与者的反馈选择发起abort或者commit命令

改进点

增加了超时机制

第二阶段,如果协调者超时没有接受到参与者的反馈,则自动认为失败,发送abort命令

第三阶段,如果参与者超时没有接受到协调者的反馈,则自动认为成功开始提交事务(基于概率)

2PC与3PC的区别

相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

5. 总结

对于涉及到多个操作需要打包执行成功或者失败的任务来说,事务的支持是不可或缺的部分,数据库作为数据保存的最终执行程序,提供完善的事务支持是必不可少的,为了支持事务InnoDB引擎提供了相当多的技术来实现,基于MVCC和行级锁可以让MySQL提供尽可能大的数据并发支持。

在分布式的环境下情况会变得非常复杂,由于网络传输和稳定性的影响,分布式事务在执行过程中可能会出现数据不一致的情况,这个是无法避免的,除非引入强一致性的分布式协议。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值