网站首页 > 技术文章 正文
本章内容
事务定义
事务指的是保证一组数据库操作,要么全部成功,要么全部失败。
在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中最小的ID
- max_trx_id:下一个要分配的事务ID
- creator_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领域,关注【南秋同学】带你一起学习成长~
猜你喜欢
- 2024-10-21 事务的4个特性以及事务的隔离级别有哪些?
- 2024-10-21 【干货】Oracle 9i、10g、11g版本的区别
- 2024-10-21 java之——数据库事务 java数据库编程中真正对事务进行控制的是
- 2024-10-21 Oracle数据库扩展语言PL/SQL之自治事务
- 2024-10-21 PostgreSQL、Oracle/MySQL和SQL Server的MVCC实现原理方式对比
- 2024-10-21 「每天一个知识点」什么是分布式事务
- 2024-10-21 这可能是最漂亮的Spring事务管理详解
- 2024-10-21 一个简单的小案例带你理解mysql中的事务
- 2024-10-21 oracle运维必备知识解析 oracle运维面试题及答案
- 2024-10-21 详解Oracle数据库UNDO表空间的作用、参数解析和管理
你 发表评论:
欢迎- 05-24网络信息安全之敏感信息在传输、显示时如何加密和脱敏处理
- 05-24常见加密方式及Python实现
- 05-24pdf怎么加密
- 05-24aes256 加密 解密 (python3) 「二」
- 05-24深入理解Python3密码学:详解PyCrypto库加密、解密与数字签名
- 05-24Springboot实现对配置文件中的明文密码加密
- 05-24JavaScript常规加密技术
- 05-24信息安全人人平等 谷歌推出低性能安卓手机加密技术
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端aes加密 (58)
- 前端md5加密 (49)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- 前端接口 (46)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- mac oracle (47)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)