龙空技术网

「一文搞懂」MySQL事务实现原理

南秋同学 374

前言:

此时兄弟们对“mysql事务的作用”都比较重视,看官们都需要知道一些“mysql事务的作用”的相关知识。那么小编也在网络上收集了一些有关“mysql事务的作用””的相关资讯,希望各位老铁们能喜欢,朋友们一起来了解一下吧!

本章内容事务定义

事务指的是保证一组数据库操作,要么全部成功,要么全部失败。

在MySQL中,事务支持是在引擎层实现的,MySQL是一个支持多引擎的系统,但并不是所有的引擎都支持事务。如:MySQL原生的MyISAM引擎就不支持事务,这也是MyISAM被InnoDB取代的重要原因之一。

事务ACID特性

事务的ACID特性指的是原子性、一致性、隔离性、持久性。

原子性(Atomicity):指的是事务中所有操作要么全部成功,要么全部失败。一致性(Consistency):指的是事务执行前后,数据始终处于一致性状态,不会出现数据丢失。隔离性(Isolation):指的是多个事务并发执行时,各事务在执行过程中所做的修改对其他事务不可见,直到该事务成功提交。持久性(Durability):指的是事务一旦提交,对数据库所做的修改将会永久保存。事务实现原理

原子性:通过undo log来实现。

持久性:通过bin log+redo log来实现。

隔离性:通过读写锁+MVCC来实现。

一致性:通过回滚、恢复以及在并发环境下的隔离做到一致性。

SQL执行流程

一条SQL语句的完整执行流程。如图所示:

其中:

步骤5:如果将参数innodb_flush_log_at_trx_commit设置成1,redo log在prepare阶段就需要调用fsync持久化一次。步骤7:将在prepare阶段写入FS Page Cache的redo log添加Commit标识,后台每秒一次轮询刷盘时将Commit标识持久化到磁盘。undo log(回滚日志)

undo log记录的是逻辑日志(即:SQL语句),它是InnoDB存储引擎用于实现多版本并发控制(MVCC)和事务回滚的一种日志记录机制。

undo log的主要作用:

回滚事务,恢复到修改前的数据。实现 MVCC(多版本并发控制,Multi-Version Concurrency Control) 。

实现原理

当一个事务开始执行时,InnoDB会为该事务分配一个事务ID。

在事务执行过程中,所有的数据修改操作(如:插入、更新和删除)都会在内存中的缓冲池(Buffer Pool)进行。同时,对于每个数据修改操作,InnoDB会将修改前的数据版本记录到undo log中。注:Buffer Pool详见《「一文搞懂」MySQL缓冲池(buffer pool)》。

undo log存储在InnoDB的表空间(.ibdata格式文件)中,其中有一块区域名为rollback segment(回滚段),每个回滚段中有1024个undo-log segment,每个undo-log segment可存储一条数据,默认有128个回滚段,即:支持128*1024条记录同时存在。

如果事务执行失败,或者需要执行rollback操作,InnoDB可以利用undo log中的记录来还原数据的原始状态(即:执行数据回滚)。

当事务提交后,如果没有其他事务需要访问这些旧版本数据,InnoDB会在适当的时候回收并重用这些undo log空间。

版本链

undo log的实现方式是通过两个隐藏列trx_id(最近一次提交事务的ID)和roll_pointer(上一个版本的地址),建立一个版本链,并在事务中读取时生成一个ReadView(读视图),在Read Committed隔离级别下,每次读取都会生成一个读视图,而在Repeatable Read隔离级别下,只会在第一次读取时生成一个读视图。

如图所示:

对应的回滚段,如图所示:

图中,age字段的当前值为8,在查询这条记录时,不同时刻启动的事务会对应不同的read-view。在read-view A、read-view B、read-view C中,age字段的值分别为1、3、8,同一条记录在系统中会存在多个版本,即:数据库的多版本并发控制(MVCC)。对于read-view A,要得到age字段值1,就必须将当前值依次执行图中所有的回滚操作。同时,即使现在有另外一个事务正在将age字段的值由8改为9,这个事务与read-view A、read-view B、read-view C对应的事务不会冲突。

在MySQL5.5及以前的版本中,回滚日志与数据字典一起放在ibdata文件中,当系统判断没有事务会使用某个版本的回滚日志时,就会删除该版本的回滚日志。

redo log和bin log

详见:《「一文搞懂」MySQL如何保证数据不丢失》一文。

事务隔离级别

当数据库上有多个事务同时执行时,可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了隔离级别的概念。SQL标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable ):

读未提交:是指一个事务还没提交时,该事务做的变更就能被其他事务看到。读提交:是指一个事务提交之后,该事务做的变更才会被其他事务看到。可重复读:是指一个事务执行过程中看到的数据,总是与该事务在启动时看到的数据一致。在可重复读隔离级别下,未提交变更对其他事务也是不可见的。串行化:是指对于同一行记录,写会加写锁,读会加读锁。当出现读写锁冲突时,后访问的事务必须等前一个事务执行完成,才能继续执行。

其中:

在可重复读隔离级别下,数据库在事务启动时创建一个视图,整个事务存在期间都使用这个视图。在读提交隔离级别下,数据库在每个SQL语句开始执行时创建一个视图。在读未提交隔离级别下,返回的是记录中的最新值,没有视图概念。在串行化隔离级别下,使用加锁的方式来避免并行访问。

读提交和可重复读区别:

在可重复读隔离级别下:在事务开始时创建一致性视图,之后事务中的其他查询都共用这个一致性视图。在读提交隔离级别下:每一个语句执行前都会重新创建一个新的视图。

MySQL默认隔离级别为:可重复读;Oracle默认隔离级别为:读提交。配置方式:启动参数 transaction-isolation,可以使用show variables来查看当前的值。

MVCC(多版本并发控制)

MySQL实现最高事务隔离级别串行化时,使用的是锁技术。在MySQL中使用的是读写锁,即:在读时加共享锁,写时加互斥锁。允许读读并行,读写以及写写都不能并行。

MVCC(Multi-Version Concurrency Control)多版本并发控制是一种并发控制机制,指维护一个数据的多个版本,使得读写操作没有冲突。具体实现就是采用快照读,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现需要依赖于数据库记录中的隐式字段、undo log版本链和Read View。

快照读和当前读

快照读:每次读取到的数据不一定是最新的数据,而是这条数据的快照版本,这样可以保证读写不互斥(读写分离),能够并发执行。

当前读:读取数据的最新版本,实现原理是对正在读的记录加锁,使得读写互斥,保证每次读取到的是数据库中最新的数据。

隐藏字段

当创建一张表时,InnoDB引擎会增加2个隐藏字段:

DB_TRX_ID:修改表数据时,都会提交事务,每个事务都有一个唯一的ID,这个字段就记录了最近一次提交事务的ID。DB_ROLL_PTR:修改表数据时,旧版本的数据都会被记录到undo log日志中,每个版本的数据都有一个版本地址,这个字段记录的就是上一个版本的地址。undo log版本链

详见:undo log章节中的版本链。

Read View(读视图)

Read View是一个保存事务ID的List列表,记录的是当前事务执行时,有哪些事务在执行,且还没有提交(即:当前系统还有哪些活跃的读写事务),用于判断当前事务是否可以读取undo log版本链中的哪个版本。

Read View基于一下几个字段实现:

m_ids:当前系统中活跃的事务ID集合(即:未提交的事务集合)。min_trx_id:m_ids中最小的IDmax_trx_id:下一个要分配的事务IDcreator_trx_id:当前事务ID

如图所示:

事务在执行快照读时,可以通过如下规则来确定undo log版本链中的哪个版本数据可见:

1)DB_TRX_ID = creator_trx_id

如果这个版本(undo log版本链中的版本)的事务ID等于当前事务ID,表示数据记录的最后一次操作事务为当前事务,当前读视图可以读到这个版本的数据。

2)DB_TRX_ID < min_trx_id

如果这个版本的事务ID小于所有活跃事务ID,表示这个版本的数据不再被事务使用(即:事务已提交),当前读视图可以读到这个版本的数据。

3)DB_TRX_ID >= max_trx_id

如果这个版本的事务ID大于等于下一个要分配的事务ID,表示有新事务更新了这个版本的数据,这种情况下,当前读视图不可以读到这个版本的数据。

4)min_trx_id <= DB_TRX_ID < max_trx_id

如果这个版本的事务ID在当前系统中活跃的事务ID集合(m_ids)中,表示这个版本的数据被其他事务更新过,当前读视图不可以读到这个版本的数据。

如果这个版本的事务ID不在当前系统中活跃的事务ID集合(m_ids)中,表示是在其他事务提交后创建的读视图,当前读视图可以读到这个版本的数据。

MVCC在四种隔离级别下的区别Read Uncommitted(读未提交):事务总是读取到最新的数据,不需要MVCC。Serializable(串行化):事务总是顺序执行,写会加写锁,读会加读锁,不需要MVCC。READ COMMITTED(读已提交):在一个事务中每一次查询都会生成一个读视图,可以读到其他事务已提交的数据,会造成不可重复度的问题。REPEATABLE READ(可重复读):在一个事务中只有第一次查询时生成一个读视图,后面的查询都是使用该读视图,避免了不可重复读的问题。常见问题

为什么建议尽量不要使用长事务?

由于长事务随时可能访问数据库中的任何数据,因此,在某个长事务提交之前,可能用到的回滚记录都必须保留在数据库中,从而占用大量存储空间。即:长事务会导致系统中存在很多很老的事务视图。

长事务除了对回滚段的影响,还占用锁资源,可能因锁资源无法及时释放而影响数据库的性能,甚至拖垮整个数据库。

【阅读推荐】

更多精彩内容(如:Redis、数据结构与算法、Nacos、G1垃圾回收器、CMS垃圾回收器、Kafka等)请移步【南秋同学】个人主页进行查阅。

【作者简介】

一枚热爱技术和生活的老贝比,专注于Java领域,关注【南秋同学】带你一起学习成长~

标签: #mysql事务的作用