事务和分布式事务

# 事务的概念

事务是一个由一系列操作组成的逻辑单元,这些操作要么全部执行成功,要么全部回滚。在数据库系统中,事务用于确保一组数据库操作的一致性和原子性。

一组 SQL 语句操作单元,组内所有 SQL 语句完成一个业务,如果整组成功,意味着全部 SQL 都实现;如果其中任何一个失败,意味着整个操作都失败。如果失败,意味着整个过程都是没有意义的,数据库应该是回到操作前的初始状态。这种特性,就叫“事务”。

# 为什么会存在事务

  1. 业务操作失败后,数据回到开始的位置,即之前所有的操作都作废
  2. 业务操作没完成之前,别的用户(进程、回话)是不能看到操作数据修改的

# 事务的特性(ACID

事务具备以下四个特性:

# 原子性(Atomicity)

功能不可再分,事务中的所有操作要么全部成功执行,要么全部回滚,没有中间状态。如果发生故障或错误,事务会进行回滚,使数据回到事务开始之前的状态。

# 一致性(Consistency)

事务执行前后,数据必须保持一致的状态。事务在执行过程中对数据进行的修改必须满足所有定义的约束和规则。

一致性是对数据可见性的约束,保证在一个事务中的多次操作的数据中间状态对其他事务是不可见的。因为这些中间状态,是一个过渡状态,与事务的开始状态和事务的结束状态是不一致的。

举个例子,张三给李四转账 100 元。事务要做的是从张三账户上减掉 100 元,李四账户上加上 100 元。一致性的含义是,要么看到张三还没有给李四转账的状态,要么张三已经成功转账给李四的状态,而对于张三少了 100 元,李四还没加上 100 元这个中间状态是不可见的。

我们来看一下转账过程中可能存在的状态:

  1. 张三未扣减、李四未收到
  2. 张三已扣减、李四未收到
  3. 张三已扣减,李四已收到

上述过程中: 1 是初始状态、2 是中间状态、3 是最终状态,1 和 3 是我们期待的状态,但是 2 这种状态却不是我们期待出现的状态。要解决 2 出现的问题,可以引入锁(Lock) 机制。但是在高并发系统中,只要加锁,必然会导致并发量下降。

原子性和一致性的的侧重点不同:

原子性关注状态,要么全部成功,要么全部失败,不存在部分成功的状态。而一致性关注数据的可见性,中间状态的数据对外部不可见,只有最初状态和最终状态的数据对外可见。

# 隔离性(Isolation)

事务的隔离性是指多个用户并发访问数据库时, 一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离,每个事务都应该感知不到其他事务的存在。隔离性确保并发执行的事务之间不会产生干扰、数据不一致或其他问题。

隔离性是多个事务的时候,,一致性是要保证操作前和操作后数据或者数据结构的一致性,而我提到的事务的一致性是关注数据的中间状态,也就是一致性需要监视中间状态的数据,如果有变化,即刻回滚

如果不考虑隔离性,事务存在 3 种并发访问数据问题,也就是脏读不可重复读虚度/幻读

# MySQL 的隔离级别

MySQL 支持 4 种隔离级别,用于控制并发事务的隔离程度和数据一致性。分别是读未提交、读已提交、可重复读、串行化。默认的隔离级别是可重复读

读未提交(Read Uncommitted):

  • 最低的隔离级别,事务可以读取其他事务尚未提交的数据。
  • 可能会发生脏读(Dirty Read)问题,即读取到未提交的数据,可能导致数据不一致。

读已提交(Read Committed):

  • 允许事务只能读取已经提交的数据,其他事务的修改对当前事务不可见。
  • 避免了脏读问题,但可能会导致不可重复读(Non-repeatable Read)问题,即在同一个事务中多次读取同一数据,结果不一致。

可重复读(Repeatable Read):

  • 默认隔离级别。保证在同一个事务中多次读取同一数据时,结果一致。
  • 通过多版本并发控制(MVCC)实现,读取的是事务开始时的一致快照数据。
  • 避免了脏读和不可重复读问题,但可能会发生幻读(Phantom Read)问题,即在同一个事务中多次查询时,结果集发生变化。

串行化(Serializable):

  • 最高的隔离级别,确保事务串行执行,避免并发问题。
  • 对所有读取的数据加锁,防止其他事务修改数据,保证数据的完整性和一致性。
  • 避免了脏读、不可重复读和幻读问题,但可能导致并发性能下降,因为事务之间需要互斥访问数据。

在 MySQL 中,可以使用以下方式配置隔离级别:

在会话级别:

使用 SET SESSION TRANSACTION ISOLATION LEVEL <隔离级别> 设置会话的隔离级别,只对当前会话生效。

在全局级别:

在 MySQL 配置文件中设置 transaction-isolation 参数,对整个 MySQL 实例生效。 需要根据具体应用的需求和并发访问的特点选择合适的隔离级别。

选择适当的隔离级别需要综合考虑数据的一致性要求并发访问的程度以及系统的性能需求。较低的隔离级别可以提高并发性能,但可能导致数据不一致;较高的隔离级别则可以保证数据的一致性,但可能影响并发性能。

# 持久性(Durability)

一旦事务提交,其所做的修改应该永久保存在数据库中,并对后续的数据操作保持可见。持久性是事务的保证,也是事务终结的标志。内存的数据必须保存到硬盘文件中。

# 分布式事务

分布式事务,是指跨越多个独立服务器或进程的事务。在分布式系统中,由于数据分布在不同的节点上,需要确保分布式事务的一致性原子性。所谓的分布式事务其实是由多个本地事务组合而成

分布式事务几乎满足不了 ACID,实际上,对于单机事务来说,大部分情况下也没有满足 ACID,不然怎么会有四种隔离级别呢?所以更别说分布在不同数据库或者不同应用上的分布式事务了。

分布式事务面临的主要挑战是协调和同步多个参与者之间的操作,以确保事务的一致性。一种常见的解决方案是使用两阶段提交协议(Two-Phase Commit,2PC)。2PC 协议分为准备阶段提交阶段。在准备阶段,事务协调者向所有参与者发出准备请求,并等待它们的响应。如果所有参与者都准备好,则进入提交阶段,事务协调者向所有参与者发出提交请求,参与者执行事务并向事务协调者发送确认。如果有任何一个参与者无法准备或确认,则事务协调者发出回滚请求,事务回滚到之前的状态。

然而,2PC 协议存在单点故障和阻塞的问题,并且在网络分区时可能导致长时间的阻塞。因此,还有其他分布式事务协议和技术,如补偿事务、最终一致性、异步复制等。这些方法根据应用场景和需求,权衡了一致性、可用性和性能等因素。

分布式事务设计和实现是一个复杂的领域,需要综合考虑多个因素。选择适当的分布式事务方案应考虑系统的需求、数据访问模式、容错能力和性能要求等因素。

使用 Hugo 构建
主题 StackJimmy 设计