当前位置: 首页 > 科技观察

MySQL事务详解

时间:2023-03-22 01:35:11 科技观察

什么是事务?事务是一个不可分割的工作单元,它要么工作要么什么都不做。从应用层面来说,一个事务对应一个完整的业务功能。从数据库层面来看,一个事务是由一批DML语句组成的。事务的分类MySQL的InnoDB存储引擎支持平面事务、带保存点的事务、链式事务和分布式事务。平面事务(FlatTransactions)平面事务使用最广泛,也最容易实现。平面事务的所有操作都在同一级别。这些操作要么全部成功,要么全部回滚。不能有部分提交或部分回滚的情况。带有保存点的平面事务(FlatTransactionswithSacepoint)平面事务的局限性在于它们不能部分回滚或提交,并且在某些场景中这样做的代价非常高。比如我们举个例子:我们玩生存类游戏,如果意外失败了,就得从出生地开始玩,这样就很郁闷了,我们希望有个游戏存档,如果游戏失败了就可以开始从最近的保存重新加载游戏。带有保存点的扁平事务,除了支持扁平事务的运行外,还允许事务在执行过程中回滚到事务更早的状态,这个更早的状态被保存点记录下来。带保存点的平面事务链式事务(ChainedTransaction)链式事务是保存点事务的一种变体。两者最大的区别在于,带有保存点的事务可以回滚到任何一个更早的保存点,而链式事务Transactions只能回滚到最新的保存点;因为有保存点的事务需要回滚到任意一个保存点,事务执行过程中占用的资源不会释放,直到当前节点执行完才会释放链上事务。最后释放掉不需要的资源,将下一个节点需要的资源传递下去。链上交易可以参考Flink流式计算的Checkpoint机制,非常相似。链式交易嵌套交易(NestedTransaction)顾名思义,交易结构看起来像一棵树,根节点是顶级交易,所有叶子节点都是扁平交易(也就是说叶子节点是真正的work.child),事务的嵌套层级不限。子事务可以提交也可以回滚,但是它们的提交不会立即生效,所有的子事务只有在顶层事务提交后才会真正提交。嵌套事务分布式事务(DistributedTransactions)分布式事务是指运行在分布式环境中的平面事务。本章主要介绍本地事务,我会在后续章节介绍分布式事务。事务的ACID特性A(Atomicity)是原子性的:整个事务操作是一个完整的不可分割的整体。只有事务中的所有操作都执行成功,才能认为事务成功。否则,必须回滚到事务执行前的状态,也就是说,做全部或不做。例如:在转账场景中,从自己账户中扣除转账金额和从对方账户中接收转账这两个操作必须是原子的。C(Consistency)一致性:事务使数据库从一种状态变为另一种状态,事务执行前后不违反数据库的完整性约束。例如:用户表的用户ID列有唯一约束,即用户ID不能重复。如果事务执行插入了相同的用户ID,就会产生不一致的状态。I(Isolation)隔离:隔离(也叫并发控制)很好理解,就是两个事务不能互相影响,也就是当前事务提交前所做的修改对其他事务不可见。在上一章中,我们谈到了MySQL锁,它可以起到控制并发的作用。D(Durability)持久性:持久性是指事务一旦提交,结果是永久的。即使服务器宕机,数据也必须能够恢复。除硬件故障和物理数据损坏外,否则必须保证交易执行结果。持久性,即保证高可靠性(HighReliability)。如何实现事务的原子性、一致性和持久性是通过redolog和undolog来完成的,事务的隔离是通过锁和MVCC来完成的。重做日志(redolog)重做日志是用来实现事务持久化的。为了更好的读写性能,InnoDB会将数据缓存在内存中,磁盘数据的修改也会滞后于内存。如果进程或系统发生崩溃,数据就有丢失的风险。这时候重做日志就起到了保证数据的一致性和持久性的作用。redolog主要以page为单位记录数据修改信息,其结构如下:redolog结构redolog在Buffer中连续写入,Buffer中的数据适时刷新到物理文件;文件顺序写入输入,每个事务的redolog追加到文件末尾;单个文件大小固定,写满后切换回文件组中的下一个文件;redolog文件组中的文件个数是固定的,写完最后一个文件后继续回到第一个文件开始写入;每个重做文件都有一个固定的2K文件头,后面跟着一个512字节的块,每个块有16字节的头尾信息;redolog有一个全局的日志序号(LSN:LogSequenceNumber),单调递增,表示事务写入的redolog的总字节数,即一个logoffset。Undolog(回滚日志)redolog记录了事务的行为,需要的时候可以“重做”页面,但是有时候事务需要回滚,当语句执行失败或者用户请求回滚时,它会为你可以使用undolog将数据回滚到修改前的状态。undolog存储行记录的更改。主要包括两种undolog:两种undolog结构insertundolog:在插入操作时产生,只对当前事务本身可见,事务提交后可以直接删除。updateundolog:由delete和update操作产生,需要提供历史版本服务于后面章节提到的MVCC,会被purge线程删除。undolog需要通过groupcommit操作将数据fsync到磁盘,以保证事务的持久性。下面是一个事务和undolog的关系结构:事务和undolog的关系结构事务隔离事务在并发场景下很难保证事务的隔离性和一致性,主要是事务的并发一致性问题。脏读:事务A从另一个并行事务B读取未提交的数据。不可重复读(Non-RepeatableRead):解决脏读问题后,可以保证读事务读取的数据是持久化数据。如果事务A多次读取同一条数据,恰好在两次读取之间,另一个并行事务B提交了这条数据的修改,导致事务A多次读取的同一数据内容不同。Phantom:类似于不可重复读,事务A多次查询一个范围,另一个并行事务B向该范围插入或删除数据并提交。事务A再次查询时,发现新增或丢失了更多的记录。LostUpdates:两个事务A和B修改了相同的数据。由于未提交的事务看不到彼此的修改,因此它们都使用旧前提更新相同的数据。写偏斜(WriteSkew):和更新损失类似,写前提变了。写偏是事务A读取了一些数据作为其他写入的前提(updateloss是针对同一个数据),但是这个时候另一个事务B修改并提交了事务A读取的数据,导致事务A做出错误的提交操作。读偏斜(ReadSkew):如果事务A读取的是两个数据的和,而事务B在事务A的读取过程中对读取的数据进行了加减,则事务A的求和结果会与实际结果不一致。针对以上并发问题,InnoDB存储引擎使用MVCC(当然MVCC本质上是一种乐观锁)和锁(锁的介绍请看我之前的文章)来解决事务隔离一致性问题。事务隔离级别事务隔离级别是MySQL对ACID实现的分类。它分为四个级别。级别越高,数据库越安全。每个隔离级别解决不同事务的并发和一致性问题。具体如下:READUNCOMMITTED(未提交读):这是最差的隔离级别。在此级别下,事务可以读取其他事务未提交的数据。也就是说,上述所有的并发一致性问题都会在这个事务隔离级别下发生。READCOMMITTED(读已提交):一个事务只能读取已提交的修改,也就是说多个并发事务之间的修改是相互不可见的。这种事务隔离级别解决了脏读问题。REPEATABLEREAD(可重复读):该级别保证在同一个事务中多次读取同一个数据的结果是一致的。该级别是InnoDB的默认隔离级别。这个隔离级别解决了脏读和不可重复读的问题,但是幻读还是有可能出现(InnoDB存储引擎在这个隔离级别下使用Next-KeyLock来解决幻读问题)。SERIALIZABLE(序列化):强制事务串行执行。没有并发,那么并发问题自然就不存在了。当然,这个级别的交易性能很低。事务隔离的实现将在后续文章中详细讲解,本文不再展开。事务的执行过程事务的执行过程查询数据,如果缓冲区不存在数据,则从磁盘加载;在数据更新前,将当前数据记录在undolog中,为后续可能的回滚做准备;BufferPool中的数据被更新;将更新后的数据写入RedoLogBuffer;准备提交事务,调用fsync将RedoLogBuffer的数据写入redolog文件,记录状态为prepared;准备提交事务,并将binlog写入磁盘中;binlog写入成功后,更新redolog的状态为commit;启用binlog后,内部会出现XA问题(binlog在MySQL层,redolog在存储引擎层),2PC(二阶段提交):prepare阶段:redolog被持久化到磁盘,同时状态设置为prepared,此时binlog不做任何操作。Commit阶段:存储引擎释放锁,是否回滚Segment,然后将binlog持久化到磁盘,然后在存储引擎层进行commit,将redolog的状态改为commit。