前面我们系统了解了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块。相信你还记得,一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块,最后到达存储引擎,但是我们的update语句,涉及到数据的持久化,我们又是怎么保证,更新操作的进行的呢?
与查询流程不一样的是,更新流程还涉及两个重要的日志模块,它们正是我们今天要讨论的主角:redo log(重做日志)和 binlog(归档日志)。如果接触 MySQL,那这两个词肯定是绕不过的,我后面的内容里也会不断地和你强调。不过话说回来,redo log 和 binlog 在设计上有很多有意思的地方,这些设计思路也可以用到你自己的程序里。
在 MySQL 里存在这个问题,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题,就采用了 MySQL 里经常说到的 WAL 技术(Write-Ahead Logging)也就是预处理日志!
具体来说,
但是InnoDB的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为==crash-safe==。
CrashSafe指MySQL服务器宕机重启后,能够保证:
例如:如果Mysql 进程异常重启了,系统会自动去检查redo log,将未写入到Mysql的数据从redo log恢复到Mysql中去。
前面我们讲过,MySQL 整体来看,其实就有两块:一块是 Server 层,它主要做的是 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。上面我们聊到的redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。
因为最开始 MySQL 里并没有 InnoDB 引擎。MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统——也就是 redo log 来实现 crash-safe 能力。
这两种日志有以下三点不同:
有了对这两个日志的概念性理解,我们再来看执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程
mysql> update T set c=c+1 where ID=2;
你可能注意到了,最后三步看上去有点“绕”,将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交"。
由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们
看看这两种方式会有什么问题?仍然用前面的 update 语句来做例子。假设当前 ID=2 的行,字段 c 的值是 0,再假设执行 update 语句过
程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?
可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。
简单说,redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
innodb_flush_log_at_trx_commit
这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数我建议你设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。sync_binlog
这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。以后我们会把读提交简称为RC,可重复读简称为RR
一般我们面临的并发场景就是如下三种
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制,解决并发读写问题
MVCC
的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的
当前读 :读取的是MySQL对应数据的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
select lock in share mode(共享锁); select for update ; update ; insert ;delete(排他锁) /*这些操作都是一种当前读
快照读 :读取的是MySQL对应数据的历史版本
select /*仅这一个操作时快照读
MVCC多版本控制指的是维持一个数据的多个版本,使得读写操作没有冲突,快照读是MySQL为了实现MVCC的一个非阻塞读功能
实现原理
隐藏字段是不可见的,除非翻阅源码 ;
这里的undo log就是上述日志中InnoDB自带的另一种日志,保存的是历史版本的数据,用以回滚
我们发现,unlog当中旧的版本数据形成了一个链表,链表首部存储的是最新的旧纪录,链表尾部存放的是最旧的旧纪录
undolog不会无限膨胀下去,会存在一个后台线程,purge线程,当发现当前记录不需要回滚且不需要参与MVCC的时候,就会把数据清
理掉
当事务在进行快照读的时候,会生成一个读视图来进行可见性判断,可见性判断是由可见性算法来确定(详聊可见性算法)
我们可以看到,事务2修改并提交的数据事务1中是可以看到的!
当我们再次进行如图测试,却的查不到我们的事务2修改并提交的数据,这是为什么?
我们看到第二次操作只是比第一次操作多执行了1次select操作,这是为什么呢?
让我们再次重温这句话:当事务在进行快照读的时候,会生成一个读视图来进行可见性判断,可见性判断是由可见性算法来确定 !
而图中我们的select就恰恰就是快照读的唯一操作,所以会生成一致性读视图即consistent read view,进行可见性判,来判断事务1能否
看到事务2修改,且已经提交的内容!
1、首先我们可见性视图有如下几个字段,所以生成可见性视图会对如下字段进行填充
2、根据第一次操作表进行填充
有了上述的内容,我们通过可见性算法对致性视图进行判断,得出结论:这个事务在Read View生成之前就已经开始commit,那么修改的结果是能够看见的。
因此,我们的操作1(图1)的操作得以解释!
3、根据第二次的操作,对表进行填充
我们的操作2进行了2次select操作,第一次select操作进行快照读的时候生成了一个一致性视图如下:
此时还未进行update操作,所以是读不到的,(注意:DB_TRX_ID是不属于ReadView的字段的,属于隐藏字段!)
接下来当我们执行完update,并且提交的时候进行select操作,会生成一个新一致性视图,但是从3行开始的操作都是一致的,生成的一致性视图也应该是与操作2的一致的呀,按理说我们应该也会生成如下的可见性视图呀!
但是我们如果是如下视图,经过可见性算法,我们应该可以看到数据的呀,所以得出结论:第二次select并未创建新的视图
总结 : 能否看到修改的数据取决于可见性算法,可见性算法比较的时候取决于readview中的结果值
因为不同隔离级别生成readView的时机是不同的 :
如果当前的所有操作都是当前读,那么是不会产生幻读问题,只有当前读和快照读一起使用的时候才会产生幻读问题
最后,我们加锁解决问题 ;
for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句“,
MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/m0_46571920/article/details/121964414
内容来源于网络,如有侵权,请联系作者删除!