这篇是mysql从头到脚系列的第二、三部,后续更新后,会同步update这边的blog~也可以点一波原作者的关注哈。本博文只是选摘了一部分,详细的点击下方原文链接查看哈。
原文地址:
第二集 日志系统
第三集 事务隔离
Mysql的日志系统
mysql本身有没有什么容灾的方式呢,或者有什么可以追溯过去操作行为的方法等。这里就要提到mysql的日志系统了,通过日志系统,往往我们也能恢复到过期一段时间内某一时刻的db数据状态。
mysql中重要的两个概念就是redo log和binlog,粗略的翻译下分别就是重做日志和归档日志。为什么需要两种日志呢?因为日志和我们的系统日志一样,最终都是要落盘的。如果每次更新操作都有一次磁盘io交互的话效率的很低的。因此mysql的innoDB引擎的日志系统就设计了一种类似归档的机制,即WAL(write-ahead-logging),先写redo,等写满或者系统不忙的时候写binlog磁盘。如果没有这个机制,那么每次对数据的更新都需要找到binglog里对应的记录并操作修改,效率低下。
redo log和binlog的不同,主要有一下几点区别:
1. redo log属于innoDB,binlog属于mysql server层的实现
2. redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 id=2 这一行的 c 字段加 1 ”
3. redolog 循环写入,包含checkpoint和writepos,记录当前归档的节点和写的位置;binlog是追加写的方式,一个文件写完了创建新的binlog文件继续写
为了保证redolog和binlog的属于强一致性,在提交的时候采用了两段式的提交方式。以给指定id的行的某一个属性+1的这样一条更新语句,更新方式如下:
执行器先找引擎取 id=2 这一行。id 是主键,引擎直接用树搜索找到这一行。如果 id=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据;
执行器调用引擎接口写入这行新数据;
引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态;
引擎告知执行器执行完成了,随时可以提交事务;
执行器生成这个操作的 binlog,并把 binlog 写入磁盘;
执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成;
事务隔离
简单来说,这里考虑的就是并发场景下mysql应对增删改查的一些方式。所谓事务,即要保证里面包含的操作同时成功或者同时失败。在mysql中,事务性是在引擎层实现的,MyISAM不支持事务,这也就是为什么它被innoDB慢慢替换掉的原因。
先回忆一下事务的ACID:Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性
隔离级别
这一章节聊的就是ACID中的隔离性。不同的事务隔离级别对应着不同的执行效率和事务安全性。按照隔离级别从低到高排序,分为读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable)四个隔离级别。
读未提交:一个事务在执行过程中能看到另一个还没有提交的事务所做的更新或者插入数据
读已提交:一个事务在执行过程中能看到其他已提交的更新或数据插入
可重复读:一个事务在执行的过程中能看到其他事务中已提交的数据,但是看不到其他事务中已提交的更新
串行化:当一个事务在操作一行数据时,另一个事务如果操作的是同一行数据数据,则另一个事务只能停下来等待。
这四种隔离级别分别处理了下面集中由于事务隔离级别导致的问题:
脏读:读到没有提交的更新的数据(读已提交解决)
不可重复读:一个事务中两个相同的查询读到了不同的结果(可重复读解决)
幻读:一个事务中读到了不同的插入数据记录,即第二次读到了另一个事务新插入的数据(串行化解决)
隔离方案
在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。