mysql事务篇
事务有哪些特性?
事务是由 MySQL 的引擎来实现的,我们常见的 InnoDB 引擎它是支持事务的。
不过并不是所有的引擎都能支持事务,比如 MySQL 原生的 MyISAM 引擎就不支持事务,也正是这样,所以大多数 MySQL 的引擎都是用 InnoDB。
事务看起来感觉简单,但是要实现事务必须要遵守 4 个特性,分别如下:
- 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样,就好比买一件商品,购买成功时,则给商家付了钱,商品到手;购买失败时,则商品在商家手中,消费者的钱也没花出去。
- 一致性(Consistency):是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。比如,用户 A 和用户 B 在银行分别有 800 元和 600 元,总共 1400 元,用户 A 给用户 B 转账 200 元,分为两个步骤,从 A 的账户扣除 200 元和对 B 的账户增加 200 元。一致性就是要求上述步骤操作后,最后的结果是用户 A 还有 600 元,用户 B 有 800 元,总共 1400 元,而不会出现用户 A 扣除了 200 元,但用户 B 未增加的情况(该情况,用户 A 和 B 均为 600 元,总共 1200 元)。
- 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的。也就是说,消费者购买商品这个事务,是不影响其他消费者购买的。
- 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?
- 持久性是通过 redo log (重做日志)来保证的;
- 原子性是通过 undo log(回滚日志) 来保证的;
- 隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;
- 一致性则是通过持久性+原子性+隔离性来保证;
事务并发问题
- 脏读:一个事务读到了另一个事务未提交的数据
- 不可重复读:一个事务读到了另一个事务**已经提交(update)**的数据。引发另一个事务,在事务中的多次查询结
果不一致。 - 虚读 /幻读:一个事务读到了另一个事务**已经插入(insert)**的数据。导致另一个事务,在事务中多次查询的结果不一致。
- 丢失更新的问题!
隔离级别(四种)
- 读未提交(*read uncommitted*),指一个事务还没提交时,它做的变更就能被其他事务看到;
- 读提交(*read committed*),指一个事务提交之后,它做的变更才能被其他事务看到;
- 可重复读(*repeatable read*),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
- 串行化(*serializable* );会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;
这四种隔离级别具体是如何实现的呢?
- 对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
- 对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
- 对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。
事务并发问题解方案
MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种:
- 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
- 针对当前读(select … for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
Read View
Read View 有四个重要的字段:
- m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务。
- min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。
- max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;
- creator_trx_id :指的是创建该 Read View 的事务的事务 id。
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:
- 如果记录的 trx_id 值小于 Read View 中的
min_trx_id值,表示这个版本的记录是在创建 Read View 前已经提交的事务生成的,所以该版本的记录对当前事务可见。 - 如果记录的 trx_id 值大于等于 Read View 中的
max_trx_id值,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见。 - 如果记录的 trx_id 值在 Read View 的
min_trx_id和max_trx_id之间,需要判断 trx_id 是否在 m_ids 列表中:- 如果记录的 trx_id 在
m_ids列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。 - 如果记录的 trx_id 不在
m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。
- 如果记录的 trx_id 在
可重复读的工作原理
可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。
读提交是如何工作的?
读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。
当前读和快照读
快照读 (Snapshot Read)
- 定义:快照读是指读取的是数据的某个历史版本,这个版本对于启动读操作的事务是一致的,即使其他事务提交了新的更改,这些更改也不会影响到当前事务看到的数据。在MySQL的InnoDB存储引擎中,这通过多版本并发控制(MVCC)来实现。
- 场景:普通的
SELECT操作,不包括任何锁定语句(如FOR UPDATE或LOCK IN SHARE MODE)。
当前读 (Current Read)
- 定义:当前读是指读取的是数据的最新版本,包括其他事务已经提交的更改。当前读操作通常涉及锁定,以防止其他事务并发地修改同一数据。
- 场景:
SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE等。
基于锁的并发控制LBCC
基于版本的并发控制MVCC
(A)undo log
insert undo log
update undo log :
(B)ReadView
什么星ReadView?
ReadView怎么产生,什么时候生成
如何判断可见性?
(C) ReadView案例分析案例
- 读已提交RC案
- 可重复读RR
小结
- MVCC指在使用RC、RR隔离级别下,使不同事务的读-写、写-读操作并发执行,提升系统性能。MVCC核心思想是读不加锁,读写不冲突,
- RC、RR这两个隔离级别的一个很大不同就是生成Readview的时机不同:
- RC在每一次进行普通 SELECT操作前都会生成一个
Readview, - RR在第一次进行普通 SELECT操作前生成一个
Readview,之后的查询操作都重复这个Readview。
- RC在每一次进行普通 SELECT操作前都会生成一个
事务回滚与数据恢复
事务的隔离级别是由MVCC和锁实现。
事务的持久性主要是通过redolog、undolog和Force Log at Commit机制机制来完成的。
- Force Log at Commit机制保证事务提交后redo log日志都已经持久化。
- undo log用于对事务的影响进行撤销,也可用于多版本控制。
- redo log用于在崩溃时恢复数据,事务回滚和数据恢复是如何实现的呢?
mysql事务篇
http://example.com/2024/02/27/数据库/mysql事务篇/