首先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账户存入转账金额,这两个操作应该是一起成功或者一起失败,如果只有一个成功了另一个失败了则会导致转账前后数据不一致,为了保证这种涉及到多个操作能够原子性的执行,就需要事务的支持。
数据库事务需要满足的ACID特性
回滚是指在无法成功执行事务的情况下通过将数据恢复到事务操作开始之前的状态来取消事务的操作,通常是使用数据副本的方式。
在某些情况下,两个事务可能会在处理过程中尝试同时访问数据的同一部分,由于操作过程中会需要申请锁住部分数据,有可能导致死锁的发生,这就类似于多线程编程中由于对同一资源的访问请求容易导致死锁的发生一样。
在没有提交机制或者使用回滚机制的系统中,可以使用补偿事务来撤销对失败事务并将系统还原回以前的状态
在MySQL数据库中,InnoDB存储引擎提供了事务的支持,数据库事务隔离级别通常分类如下四种隔离级别
MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能
在innodb中“MVCC多版本一致性读”功能的实现是基于undo-log的。主要是为Repeatable-Read事务隔离级别做的。在此隔离级别下,事务A,B相互隔离,互相操作的数据互不可见。
innodb存储的最基本row中包含一些额外的存储信息,DATA_TRX_ID, DATA_ROLL_PTR, DB_ROW_ID, DELETE_BIT
MySQL事务在开始到提交过程中,都会被保存在一个叫做trx_sys的事务链表中
+---------+ +---------+ +----------+
| TRX1 +----->+ TRX2 +---->+ TRX3 |
+---------+ +---------+ +----------+
事务链表中都是未提交的事务,如果事务一旦被提交则会被事务链表删除。
ReadView是一个用于事务控制的数据结构,包含3个主要成员:ReadView{low_trx_id,up_trx_id,trx_ids},用于表识事务启动过程中哪些部分是可见的,各成员代表意思如下:
注意,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相同的值即可。
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日志的大小是固定的,即记录满了以后就从头循环写。
保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读
binlog是属于MySQL Server层面的,又称为归档日志,属于逻辑日志,是以二进制的形式记录的是这个语句的原始逻辑,依靠binlog是没有crash-safe能力的
redo log是属于innoDB层面,binlog属于MySQL Server层面的,这样在数据库用别的存储引擎时可以达到一致性的要求。
redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录的是这个更新语句的原始逻辑
redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。
undo log不是redo log的逆向过程,其实它们都算是用来恢复的日志:
1.redo log通常是物理日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
2.undo用来回滚行记录到某个版本。undo log一般是逻辑日志,根据每行记录进行记录。
分布在系统中不同实例的处理单元执行一系列的操作,并且这些操作需要确保同时成功或者同时失败,解决分布式事务的关键就是必须要有一种方法能够感知事务在任何地方所做的任何改动,提交或者回滚的决定必须产生统一的结果。
尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于MySQL是从5.5开始支持。
总的来说,XA协议比较简单,成本较低,但是其单点问题,以及不能支持高并发(由于同步阻塞)依然是其最大的弱点。
主要分三个阶段
改进点
增加了超时机制
第二阶段,如果协调者超时没有接受到参与者的反馈,则自动认为失败,发送abort命令
第三阶段,如果参与者超时没有接受到协调者的反馈,则自动认为成功开始提交事务(基于概率)
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
对于涉及到多个操作需要打包执行成功或者失败的任务来说,事务的支持是不可或缺的部分,数据库作为数据保存的最终执行程序,提供完善的事务支持是必不可少的,为了支持事务InnoDB引擎提供了相当多的技术来实现,基于MVCC和行级锁可以让MySQL提供尽可能大的数据并发支持。
在分布式的环境下情况会变得非常复杂,由于网络传输和稳定性的影响,分布式事务在执行过程中可能会出现数据不一致的情况,这个是无法避免的,除非引入强一致性的分布式协议。