MySQL索引、锁、事务、MVCC、分库分表常见问题总结笔记(四) MySQL事务 锁 MVCC ACID

 

MVCC多版本并发控制

维持一个数据的多个版本,使得读写操作没有冲突一种用来解决读-写冲突的无锁并发控制优点:提升读写效率

MVCC,全称 Multi-Version Concurrency Control ,即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存

基础知识

当前读:读取的是数据最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

快照读:读取的是历史版本记录不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即 MVCC ,可以认为 MVCC 是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本

 

事务操作可能会出现的数据问题

脏读
脏读说的是事务知道了自己本不应该知道的东西,强调的动作是查询事务A修改了数据,但未提交,而事务B查询了事务A修改过却没有提交的数据,这就是脏读,因为事务A可能会回滚


不可重复读
不可重复读强调的是一个人查的时候,其他人却可以增删改, 但我却不知道数据被改了,还拿去做了之前的用途。事务A 先 查询了金额,是200块钱,未提交 。事务B在事务A查询完之后,修改了金额,变成了300, 在事务A前提交了;如果此时事务A再查询一次数据,就会发现钱跟上一次查询不一致,是300,而不是200。这就是不可重复读。


幻读
读强调的是我修改了数据,等我要查的时候,却发现有我没有修改的记录,为什么,因为有其他人了一条新的。事务A先修改了某个表的所有纪录的状态字段为已处理,未提交;事务B也在此时新增了一条未处理的记录,并提交了;事务A随后查询记录,却发现有一条记录是未处理的,很是诧异,刚刚不是全部修改为已处理嘛,以为出现了幻觉,这就是幻读

 事务的隔离级别的标准

级别隔离强大:读未提交<读已提交<可重复读<串行

Read Uncommitted 读未提交
限制最弱的事务级别,忽略其他事务放置的锁,该级别下的事务可以在读取其他事务修改后(插/删/更)但未提交的的数据;说白了这个级别的事务就是个弱鸡,只能保证多个操作的原子性,完整不能解决并发问题;无法解决脏读,不可重复读,幻读

Read Committed 读已提交
SQL Server默认级别,指定事务执行期间(未提交)不能读取其他事务还未提交的数据,禁止脏读;但可以读取到其他事务 (插/删/更)操作后并提交了的 数据;从而造成多次查询的数据不一致,即不可重复读,同时也不法避免幻读,因为当前事务执行期间,其他事务可以插入新记录

Repeatable Read 可重复读
MySQL的默认级别,事务执行期间(未提交)不能读取其他事务还未提交的数据,也不能其他事务读取更新操作后并提交的数据,所以可以避免脏读和不可重复读;但是可以读取到其他事务插/删操作并提交的数据,因此因为可以读取到其他事务插入并提交的新数据,所以可能会造成幻读

Serializable 事务同步,串行
最严格的事务管理,等于事务同步执行,不允许并发,所以不存在并发问题,所有问题都解决了,但是存在性能问题,执行数据慢

 

 

关于快照读为什么无法读取已提交数据?

一、第一部分:隐藏字段每一行记录都会包含几个隐藏字段

        1.DB_TRX_ID创建或最后一次修改该记录的事务id

        2.DB_ROW_ID隐藏主键,插入数据时,需要和索引绑定,先绑主键,没有主键绑唯一,没有唯一键就创建rowId索引并绑定。

        3.DB_ROLL_PTR回滚指针,指向这条记录的上一个版本,与undolog有关。

二、第二部分:undolog,回滚日志

    首先,在开启事务进行时,会预先保存目标数据的历史版本,叫undolog

insert undo log
代表事务在 insert 新记录时产生的 undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log
事务在进行 update 或 delete 时产生的 undo log ; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除

    然后在事务期间,需要回滚时,会通过DB_ROLL_PTR存储的指向undolog的地址使目标数据回滚到事务前的历史记录。

 

三、第三部分:readview事务在进行快照读的适合产生的读视图

在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID。Read View 主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个 Read View 读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据

Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的 DB_TRX_ID(即当前事务 ID )取出来,与系统当前其他活跃事务的 ID 去对比(由 Read View 维护),如果 DB_TRX_ID 跟 Read View 的属性做了某些比较,不符合可见性,那就通过 DB_ROLL_PTR 回滚指针去取出 Undo Log 中的 DB_TRX_ID 再比较,即遍历链表的 DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的 DB_TRX_ID , 那么这个 DB_TRX_ID 所在的旧记录就是当前事务能看见的最新老版本

可以把 Read View 简单的理解成有三个全局属性

trx_list

    一个数值列表
    用于维护 Read View 生成时刻系统 正活跃的事务 ID 列表

up_limit_id

    lower water remark
    是 trx_list 列表中事务 ID 最小的 ID

low_limit_id

    hight water mark
    ReadView 生成时刻系统尚未分配的下一个事务 ID ,也就是 目前已出现过的事务 ID 的最大值 + 1
    为什么是 low_limit ? 因为它也是系统此刻可分配的事务 ID 的最小值

  1. 首先比较 DB_TRX_ID < up_limit_id , 如果小于,则当前事务能看到 DB_TRX_ID 所在的记录,如果大于等于进入下一个判断
  2. 接下来判断 DB_TRX_ID >= low_limit_id , 如果大于等于则代表 DB_TRX_ID 所在的记录在 Read View 生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断
  3. 判断 DB_TRX_ID 是否在活跃事务之中,trx_list.contains (DB_TRX_ID),如果在,则代表我 Read View 生成时刻,你这个事务还在活跃,还没有 Commit,你修改的数据,我当前事务也是看不见的;如果不在,则说明,你这个事务在 Read View 生成之前就已经 Commit 了,你修改的结果,我当前事务是能看见的

在可重复度隔离级别下,事务能否通过快照读读取到其他事务已经提交的数据?

  • 当事务 2对某行数据执行了快照读,数据库为该行数据生成一个Read View读视图,假设当前事务 ID 为 2,此时还有事务1和事务3在活跃中,事务 4在事务 2快照读前一刻提交更新了,所以 Read View 记录了系统当前活跃事务 1,3 的 ID,维护在一个列表上,假设我们称为trx_list
  • Read View 不仅仅会通过一个列表 trx_list 来维护事务 2执行快照读那刻系统正活跃的事务 ID 列表,还会有两个属性 up_limit_id( trx_list 列表中事务 ID 最小的 ID ),low_limit_id ( 快照读时刻系统尚未分配的下一个事务 ID ,也就是目前已出现过的事务ID的最大值 + 1 资料传送门 | 呵呵一笑百媚生的回答 ) 。所以在这里例子中 up_limit_id 就是1,low_limit_id 就是 4 + 1 = 5,trx_list 集合的值是 1, 3,Read View 如下图
  • 我们的例子中,只有事务 4 修改过该行记录,并在事务 2 执行快照读前,就提交了事务,所以当前该行当前数据的 undo log 如下图所示;我们的事务 2 在快照读该行记录的时候,就会拿该行记录的 DB_TRX_ID 去跟 up_limit_id , low_limit_id 和活跃事务 ID 列表( trx_list )进行比较,判断当前事务 2能看到该记录的版本是哪个。

所以先拿该记录 DB_TRX_ID 字段记录的事务 ID 4 去跟 Read View 的 up_limit_id 比较,看 4 是否小于 up_limit_id( 1 ),所以不符合条件,继续判断 4 是否大于等于 low_limit_id( 5 ),也不符合条件,最后判断 4 是否处于 trx_list 中的活跃事务, 最后发现事务 ID 为 4 的事务不在当前活跃事务列表中, 符合可见性条件,所以事务 4修改后提交的最新结果对事务 2 快照读时是可见的,所以事务 2 能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本

 ACID

一致性由其他三个特性支撑;

WAL(Write Ahead Log)预写日志,是数据库系统中常见的一种手段,用于保证数据操作的原子性和持久性。

如何做到数据的可恢复(原子性)和提交成功的数据被持久化到磁盘(持久性)?

    WAL 机制的原理也很简单:「修改并不直接写入到数据库文件中,而是写入到另外一个称为 WAL 的文件中如果事务失败,WAL 中的记录会被忽略,撤销修改;如果事务成功,它将在随后的某个时间被写回到数据库文件中,提交修改。」

Mysql提供的日志文件:binlog,二进制日志文件。

二阶段提交的来由:

 数据更新的流程:

场景1:

    prepare阶段突然断电等,因为redolog和binlog的数据不一致,所以事务回滚;

场景2:

    写入redolog和写binlog成功,再断电;恢复的时候发现两日志文件记录一致,将prepare状态改为commit。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值