面试被问到mvcc,一脸懵逼,尴尬。查了查,记录下来;
事务特性ACID
原子性(Atomicity): 事务的操作不可分割,共同失败或成功。
隔离性(Isolation): 不同事物间相互不影响,称为隔离性,但是各个数据库实现时并不是直接完全隔离,事务执行的中间结果对其他事务是透明的,方便灵活使用
一致性(Consistency): 数据成功或者失败后和预设结果一致,即几个并行执行的事务,执行结果与按照串行执行结果一致。
**持久性(Durability):**事务成功后数据会持久化到磁盘,不会丢失。
隔离级别
事务的ACID的特性说了事务具有隔离性,而不同事务的隔离基本做法就是加锁。通过对锁的控制来实现事务的隔离性,其中Mysql默认的隔离级别是重复读(RR),而Oracle、Sql Server则是读已提交(RC);
读未提交: 可以读取其他事务未提交的数据,这个隔离性是最差的,一旦数据库的一条数据同时被多人操作就会出现问题
读已提交: 可以读取其他事务已经提交的数据,这个是Oracle的默认隔离级别
重复读: 不会读取到其他事务已经提交的数据(指当前事务已经读取到的数据),读到的数据都是本事务开始时的快照信息,被称为快照读。
**串行化:**所有事物完全隔离,互相不干扰,这也是最严格的隔离级别,注意串行化不同事物的读写也会阻塞。
参考 Mysql夺命50问-你的面试宝典_mysql explain 面试题-CSDN博客
事务问题分类:
脏读: 读到了其他事务未提交的数据
**不可重复读:**在一个事务操作中,读取到了其他事务提交的数据。
**幻读:**读到了其他事务新增的数据
隔离级别存在的问题
读未提交: 有脏读(读到了其他事务未提交的修改数据)、不可重复读(读到了其他事务已提交的修改数据)、幻读(读到了其他事务新增的数据)等全部问题,这个隔离级别啥问题都解决不了,所以没人用
读已提交: 解决脏读问题,但仍有不可重复读和幻读的问题
重复读: 解决脏读、不可重复读的问题,仍有幻读的问题
**串行化:**解决全部问题,就是效率太低
mvcc
什么是mvcc
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制,是一种并发控制的方法,;
解决问题:
- 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
- 解决脏读、幻读、不可重复读等事务隔离问题,但是不能解决更新丢失问题
mvcc原理
主要依赖于数据记录中的三个隐藏字段、undolog,read view来实现的。
1、隐藏字段
每行记录,除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段:
- DB_ROW_ID:6字节,隐藏的主键,如果数据表没有主键,那么innodb会自动生成一个6字节的row_id
- DB_TRX_ID:6字节,最近修改事务id,记录创建这条记录或者最后一次修改该记录的事务id
- DB_ROLL_PTR:7字节,回滚指针,用于配合undo日志,指向上一个旧版本
假设记录如图所示:
2. undo log(回滚日志)
undo log是InnoDB的行为,主要用途是用于事务回滚和MVCC使用,在事务开始之前数据就会记录数据到undolog buffer(Buffer Pool中也是数据页形式存在)中,记录的是数据修改前的数据;
3. read view(快照读)
Read View是事务进行快照读操作的时候生产的读视图,在该事务执行快照读的那一刻,系统会生成一个此刻的快照,记录并维护系统此刻活跃事务的id,用来做可见性判断的,也就是说当某个事务在执行快照读的时候,对该记录创建一个Read View的视图,把它当作条件去判断当前事务能够看到哪个版本的数据,有可能读取到的是最新的数据,也有可能读取到的是当前行记录的undolog中某个版本的数据
1)可见性算法
将要被修改的数据的最新记录中的DB_TRX_ID(当前事务id)取出来,与系统此刻其他活跃事务的id去对比,如果DB_TRX_ID跟Read View的属性做了比较,不符合可见性,那么就通过DB_ROLL_PTR回滚指针去取出undolog中的DB_TRX_ID做比较,即遍历链表中的DB_TRX_ID,直到找到满足条件的DB_TRX_ID,这个DB_TRX_ID所在的旧记录就是当前事务能看到的数据。
2)可见性规则
首先要知道Read View中的三个全局属性:
- trx_list:一个数值列表,用来维护Read View生成时刻系统正活跃的事务ID(1,2,3)
- up_limit_id:记录trx_list列表中事务ID最小的ID(1)
- low_limit_id:Read View生成时,系统即将分配的下一个事务ID(4)
当前读
读取的是最新版本的记录,读取时还要保证其它并发事务不能修改当前记录,会对读取的记录进行加锁
- 共享锁:select lock in share mode
- 排它锁:select for update 、update、 insert 、delete
RR 和RC 的快照读有什么区别
RR (可重复读)隔离级别下,则是同一个事务中的第一个快照读才会创建Read View,之后的快照读获取的都是同一个Read View.
在RC(读已提交)隔离级别下,是每个快照读都会生成并获取最新的Read View;
其他记录
mysql的日志
三种日志的区别:undo log、redo log 、bin log
其他日志:relay log(中继日志) 、general query log(一般查询日志)、慢查询日志、error log(错误日志)
Buffer pool(缓冲池)
InnoDB设计了一个缓冲池(Buffer Pool),来提高数据库的读写性能。
当读取数据时,如果数据存在于Buffer pool中,那么直接从Buffer pool中拿,否则从磁盘中拿。
修改数据时,如果数据存在于Buffer pool中,那么直接修改Buffer pool中数据所在的页,然后将其页设置为脏页,为了减少磁盘IO,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入磁盘。
redo log
断电丢失问题:Buffer pool是基于内存的,如果断电重启,还未来的及写入磁盘的脏页数据就会丢失。 问题如何解决/
解决问题方法:
为了防止断电丢失的问题,当有一条记录需要更新时,InnoDB引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以redo log的形式记录下来,这个时候,更新就算完成。后续由后台线程选择一个合适的时机将脏页写入磁盘。
这就是:WAL(Write-Ahead Logging)技术:MySQL的写操作并不是立刻写在磁盘上,而是先写日志,然后在合适的时间再写入磁盘。
binlog 归档日志
undo log 和 redo log 是由InnoDB存储引擎 产生的
binlog 是由Server层 产生的,记录全部库表和数据的修改记录(不记录查询操作)
binlog有三种格式类型
STATEMENT(默认格式):记录SQL语句,但是存在动态函数的问题,比如获取当期时间、uuid
ROW:记录行数据最终被修改成什么样了。如果批量执行update语句,那可能会导致binlog文件过大。
MIXED:包含了上述两种模式,根据不同情况自动使用ROW和STATEMENT
bin log和redo log的区别:
-
层次不同:redo log 是InnoDB存储引擎实现的,bin log 是MySQL的服务器层实现的,但MySQL数据库中的任何存储引擎对于数据库的更改都会产生bin log。
-
作用不同:redo log 用于碰撞恢复(
crash recovery
),保证MySQL宕机也不会影响持久性;bin log 用于时间点恢复(point-in-time recovery
),保证服务器可以基于时间点恢复数据和主从复制。 -
内容不同:redo log 是物理日志,内容基于磁盘的页**
Page
** ;bin log的内容是二进制,可以根据**binlog_format
**参数自行设置。 -
写入方式不同:redo log 采用循环写的方式记录;binlog 通过追加的方式记录,当文件大小大于给定值后,后续的日志会记录到新的文件上。
-
刷盘时机不同:bin log在事务提交时写入;redo log 在事务开始时即开始写入。
参考文章