文章目录
- 表记录读取的两种方式
- 举个栗子
- MySQL是如何避免幻读的
- bin log/redo log/undo log
- MVCC概述
- MVCC实现原理
表记录读取的两种方式
- 快照读:读取的是快照版本。普通的SELECT就是快照读。通过mvcc来进行并发控制的,不用加锁。
- 当前读:读取的是最新版本。
UPDATE
、DELETE
、INSERT
、SELECT … LOCK IN SHARE MODE
、SELECT … FOR UPDATE
是当前读。
注意注意注意!!!!:快照读情况下,InnoDB在RR隔离级别下通过mvcc机制避免了幻读现象
,而mvcc机制无法避免当前读情况下出现的幻读现象。因为当前读每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。
举个栗子
下面举个例子说明下:t_uesr表
事务A | 事务B |
---|---|
start transaction | start transaction |
select * from t_user where user_name=‘a’; 假设有一条 | |
insert into t_user(user_name, user_password, user_mail, user_state) values(‘aismall’, ‘a’, ‘a’, 0); | |
commit | |
select * from t_user where user_name=‘a’; 此时就能查到两条 | |
commit |
MySQL是如何避免幻读的
在快照读情况下:MySQL通过mvcc来避免幻读。
在当前读情况下:MySQL通过next-key来避免幻读(加行锁和间隙锁来实现的),其中,next-key包括两部分,行锁和间隙锁,行锁是加在
索引上的锁,间隙锁是加在索引之间的。
Serializable隔离级别也可以避免幻读,会锁住整张表,并发性极低,一般不会使用。
bin log/redo log/undo log
MySQL日志主要包括查询日志、慢查询日志、事务日志、错误日志、二进制日志等。其中比较重要的是 bin log(二进制日志)和 redo log(重做日志)和 undo log(回滚日志)。
bin log:bin log是MySQL数据库级别的文件,记录对MySQL数据库执行修改的所有操作,不会记录select和show语句,主要用于恢复数据库和同步数据库。
redo log:redo log是innodb引擎级别,用来记录innodb存储引擎的事务日志,不管事务是否提交都会记录下来,用于数据恢复。当数据库发生故障,innoDB存储引擎会使用redo log恢复到发生故障前的时刻,以此来保证数据的完整性。将参数innodb_flush_log_at_tx_commit设置为1,那么在执行commit时会将redo log同步写到磁盘。
undo log:除了记录redo log外,当进行数据修改时还会记录undo log,undo log用于数据的撤回操作,它保留了记录修改前的内容。通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC。
小结:
- bin log会记录所有日志记录,包括InnoDB、MyISAM等存储引擎的日志;redo log只记录innoDB自身的事务日志。
- bin log只在事务提交前写入到磁盘,一个事务只写一次;而在事务进行过程,会有redo log不断写入磁盘。
- bin log是逻辑日志,记录的是SQL语句的原始逻辑;redo log是物理日志,记录的是在某个数据页上做了什么修改。
MVCC概述
MVCC(Multiversion concurrency control) :多版本并发控制
- 多版本:指MySQL维护着行数据的多个版本。
- 并发控制:多个事务同时操作某一行记录时,由MySQL控制返回多个版本的行记录中的某个版本。
MVCC实现原理
MVCC实现原理主要通过下面这个三个来实现:隐藏字段,undo log 版本链,read view。
隐藏字段是什么:
- 在undo log中每条数据的记录大概是这样的:
DB_TRX_ID
:当前事务id,通过事务id的大小判断事务的时间顺序。DB_ROLL_PTR
:回滚指针,指向当前行记录的上一个版本,通过这个指针将数据的多个版本连接在一起构成undo log版本链
。DB_ROW_ID
:主键,如果数据表没有主键,InnoDB会自动生成主键。
使用事务更新行记录的时候,就会生成版本链
,执行过程如下:
- 1.用排他锁锁住该行;
- 将该行原本的值拷贝到undo log,作为旧版本用于回滚;
- 修改当前行的值,生成一个新版本,更新事务id,使回滚指针指向旧版本的记录,这样就形成一条版本链。
下面举个例子方便理解:
- 1、初始数据如下,其中DB_ROW_ID和DB_ROLL_PTR为空。
- 2、事务A对该行数据做了修改,将age修改为19,效果如下:
- 3、之后事务B也对该行记录做了修改,将age修改为20,效果如下:
- 4、此时undo log有两行记录,并且通过回滚指针连在一起。
read view:也可理解为数据的一份快照。上面undo log 中有这么多版本,具体要返回那个版本就是由read view来决定的,其中read view 中维护了下面这些东西:
trx1...trxn
:当前活跃事务id的集合(未提交事务)。up_limit_id
:当前活跃事务的最小事务id。low_limit_id
:当前活跃事务的最大事务id。- read view创建着事务id。
不同隔离级别创建read view的时机不同: - read committed:每次执行select都会创建新的read_view,保证能读取到其他事务已经提交的修改。
- repeatable read:在一个事务范围内,第一次select时更新这个read_view,以后不会再更新,后续所有的select都是复用之前的read_view。这样可以保证事务范围内每次读取的内容都一样,即可重复读。
read view是通过一个可见性算法(对比规则)来决定要返回那个版本的,这个算法就是使用当前的事务ID(DATA_TRX_ID
)跟 read view进行对比,具体对比规则如下:
- 如果
DATA_TRX_ID < up_limit_id
:说明在创建read view时,修改该数据行的事务已提交,该版本的记录可被当前事务读取到。 - 如果
DATA_TRX_ID >= low_limit_id
:说明当前版本的记录的事务是在创建read view之后生成的,该版本的数据行不可以被当前事务访问。此时需要通过版本链找到上一个版本,然后重新判断该版本的记录对当前事务的可见性。 - 如果
up_limit_id <= DATA_TRX_ID < low_limit_id
:需要在活跃事务链表中查找是否存在ID为DATA_TRX_ID的值的事务。如果存在,因为在活跃事务链表中的事务是未提交
的,所以该记录是不可见的。此时需要通过版本链找到上一个版本,然后重新判断该版本的可见性。如果不存在,说明事务trx_id 已经提交了,这行记录是可见的。
总结:InnoDB 的MVCC是通过 read view 和版本链实现的,版本链保存有历史版本记录,通过read view 判断当前版本的数据是否可见,如果不可见,再从版本链中找到上一个版本,继续进行判断,直到找到一个可见的版本。