MySql日志以及分布式事务的解决方式

undo日志和redo日志 在数据库系统中,既有存放数据的文件,也有存放日志的文件…

undo日志和redo日志

在数据库系统中,既有存放数据的文件,也有存放日志的文件。日志在内存中也是有缓存Log buffer,也有磁盘文件log file。MySQL中的日志文件,有这么两种与事务有关:undo日志与redo日志。

undo日志

数据库事务具备原子性(Atomicity),如果事务执行失败,需要把数据回滚。

原子性可以利用undo日志来实现。Undo Log的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到Undo Log。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。数据库写入数据到磁盘之前,会把数据先缓存在内存中,事务提交时才会写入磁盘中。用Undo Log实现原子性和持久化的事务的简化过程:

缺陷:每个事务提交前将数据和Undo Log写入磁盘,这样会导致大量的磁盘IO,因此性能很低。

如果能够将数据缓存一段时间,就能减少IO提高性能。但是这样就会丧失事务的持久性。因此引入了另外一种机制来实现持久化,即Redo Log.

和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化,减少了IO的次数。

分布式事务

在数据库水平拆分、服务垂直拆分之后,一个业务操作通常要跨多个数据库、服务才能完成。在分布式网络环境下,我们无法保障所有服务、数据库都百分百可用,一定会出现部分服务、数据库执行成功,另一部分执行失败的问题。当出现部分业务操作成功、部分业务操作失败时,业务数据就会出现不一致。

例如:付款表与库存表在不同数据库,用户购买时扣减金额失败了,付款表回滚,但是库存表因为没有与商品表在同一个数据库中,并不会发生回滚。

解决分布式事务的思路

CAP定理和BASE理论可以参考之前文章

解决思路一:分阶段提交

1994 年,X/Open 组织(即现在的 Open Group )定义了分布式事务处理的DTP 模型。该模型包括这样几个角色:

  • 应用程序( AP ):我们的微服务

  • 事务管理器( TM ):全局事务管理者

  • 资源管理器( RM ):一般是数据库

  • 通信资源管理器( CRM ):是TM和RM间的通信中间件

在该模型中,一个分布式事务(全局事务)可以被拆分成许多个本地事务,运行在不同的AP和RM上。每个本地事务的ACID很好实现,但是全局事务必须保证其中包含的每一个本地事务都能同时成功,若有一个本地事务失败,则所有其它事务都必须回滚。但问题是,本地事务处理过程中,并不知道其它事务的运行状态。因此,就需要通过CRM来通知各个本地事务,同步事务执行的状态。

二阶段提交

阶段一:准备阶段,各个本地事务完成本地事务的准备工作

阶段二:执行阶段,各个本地事务根据上一阶段执行结果,进行提交或回滚

这个过程中需要一个协调者(coordinator),还有事务的参与者(voter)

投票阶段:协调组询问各个事务参与者,是否可以执行事务。每个事务参与者执行事务,写入redo和undo日志,然后反馈事务执行成功的信息(agree),若失败返回disagree

提交阶段:协调组发现每个参与者都可以执行事务(agree),于是向各个事务参与者发出commit指令,各个事务参与者提交事务,否则回滚。

缺陷:

单点故障问题:协调组若挂掉,那么将无法判断接下来是提交还是回滚

阻塞问题:在准备阶段、提交阶段,每个事物参与者都会锁定本地资源,并等待其它事务的执行结果,阻塞时间较长,资源锁定时间太久,因此执行的效率就比较低了。

解决思路二:TCC模式

它本质是一种补偿的思路。事务运行过程包括三个方法,

  • Try:资源的检测和预留;

  • Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;

  • Cancel:预留资源释放。

执行分两个阶段:

  • 准备阶段(try):资源的检测和预留;

  • 执行阶段(confirm/cancel):根据上一步结果,判断下面的执行方法。如果上一步中所有事务参与者都成功,则这里执行confirm。反之,执行cancel

try、confirm、cancel都是独立的事务,不受其它参与者的影响,不会阻塞等待它人

例如:假设账户A原来余额是100,需要余额扣减30元。

缺点:需要人为编写代码实现try、confirm、cancel,代码侵入较多,同时增大了开发复杂度。同时cancel动作如果执行失败,资源就无法释放,需要引入重试机制,而重试可能导致重复执行,还要考虑重试时的幂等问题

解决思路三:使用MQ

事务发起者A执行本地事务,通过MQ将需要执行的事务信息发送给事务参与者B,事务参与者B接收到消息后执行本地事务。利用mq的消息可靠性实现,同时事务参与者B必须确保消息最终一定能消费,如果失败需要多次重试

缺点:依赖于MQ的可靠性、消息发起者可以回滚,但是消息参与者无法引起事务回滚、事务时效性差,取决于MQ消息发送是否及时

解决思路四:AT模式

类似于TCC模式,都是分为两个阶段,但是不需要自己编写第二阶段代码,通过Seata实现。

在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后获取全局行锁,提交事务。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

这里的before imageafter image类似于数据库的undo和redo日志,但其实是用数据库模拟的。

二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据。

本文来自网络,不代表软粉网立场,转载请注明出处:https://www.rfff.net/p/5116.html

作者: HUI

发表评论

您的电子邮箱地址不会被公开。

返回顶部