1.事务的四大特性
ACID:
A:Atomicity,原子性,要么全部执行,要么全部都不执行。
C:Consistency,一致性,满足现实世界业务的约束。
I :Isolation,隔离性,并行事务之间互不影响。
D:Durability,持久性,事务一旦提交永久保留。
2.四大隔离级别
READ UNCOMMITTED:未提交读,可能发生“脏读、不可重复读和幻读”问题
READ COMMITTED:RC,已提交读,可能发生“不可重复读和幻读”问题
REPEATABLE READ:RR,可重复读,可能发生“幻读”问题[mysql 默认]
SERIALIZABLE:可串行化,上述问题都不可能发生
3.什么是脏页
脏页发生在InnoDB的Buffer Pool(缓冲池)中,当数据发生修改的时候,一般先修改Buffer Pool的缓存页,再由缓存页去把数据刷到磁盘。将修改数据后,并且未刷盘的页,称为脏页。
4.持久型原理-redo
事务的持久性是指事务一旦提交永久保留,即使数据库宕机,也不会丢失;即使服务器宕机,也不会丢失。
InnoDB的内存管理是通过Buffer Pool来进行刷盘存储数据,如果此时发生宕机就会丢失数据,而redo日志机制就是用来保证mysql宕机时不丢失数据。
redo日志:也叫重做日志,针对记录修改的日志,在系统崩溃后重启时,按照此日志重新执行,便能够复原没有被持久化的数据页。
格式如下图:
日志占用的空间非常小,主要存储的是:存储表空间ID、页号、偏移量以及需要更新的值。
日志是顺序写入磁盘的,发生连续I/O的几率更高。
redo日志不是一个日志文件,而是多个日志文件,因为大小有限制,需要循环写入,工作原理如下:
图中,0号日志文件已经刷盘,若此时发生异常需要重做,则只需要写入1号和2号日志文件即可。
5.事务原子性原理-undo
何时发生undo?当执行ROLLBACK的SQL语句时,记录需要回滚至之前的状态,那么便需要将记录之前的样子保存下来。这时就需要undo日志
undo日志:又叫撤销日志,在记录的修改之前都会记录记录之前状态的快照,记录的快照即为undo日志。
在行格式中,隐藏列有一个回滚指针(roll_point),指向undo日志:
undo日志链(又叫版本链)工作原理如下:
当执行ROLLBACK时,找到上一个事务对应的undo日志记录,并执行复原。
6.事务的隔离性原理 undo+MVCC
MVCC:多版本并发控制,借助undo日志构造的版本链,实现对数据库的并发访问。
主要借助一些辅助信息例如上图中的(trx_id),并通过比对当前事务ID与版本链的数据ID来判断哪个版本的记录对当前事务可见。
在InnoDB内存中有一种数据结构ReadView,用来判断记录是否对当前事务可见。
ReadView主要构成内容:
m_ids:未提交的事务ID列表。
min_trx_id:最小ID,未提交的事务ID列表
max_trx_id:最大ID,下一个分配事务的id值。
creator_trx_id:当前事务ID。
何时生成ReadView?
1.READ COMMITTED RC:每次读取数据前都生成一个ReadView。(需要解决脏读)
2.REPEATABLE READ RR:第一次读取数据时生成一个ReadView,之后复用。(需要解决脏读和不可重复读)
如何判断是否可见?
trx_id = creator_trx_id:意味着当前事务在访问它自己修改过的记录(可见)
trx_id < min_trx_id:表明生成该版本的事务在当前事务生成ReadView前已经提交(可见)
trx_id >= max_trx_id:表明生成该版本的事务在当前事务生成ReadView后才开启(不可见)
min_trx_id <= trx_id < max_trx_id:判断一下trx_id属性值是不是在m_ids列表中
如果在,说明创建ReadView时生成该版本的事务还未提交(不可见)
如果不在,说明创建ReadView时生成该版本的事务已经被提交(可见)
7.什么是脏读、不可重复读、幻读
脏读:
Session A和Session B各开启了一个事务,Session B中的事务先将number列为1的记录的name列更新为'关羽',然后Session A中的事务再去查询这条number为1的记录,如果读到列name的值为'关羽',而Session B中的事务稍后进行了回滚,那么Session A中的事务相当于读到了一个不存在的数据。
不可重复读:
在Session B中提交了几个隐式事务(注意是隐式事务,意味着语句结束事务就提交了),这些事务都修改了number列为1的记录的列name的值,每次事务提交之后,如果Session A中的事务都可以查看到最新的值。
幻读:
Session A中的事务先根据条件number > 0这个条件查询表hero,得到了name列值为'刘备'的记录;之后Session B中提交了一个隐式事务,该事务向表hero中插入了一条新记录;之后Session A中的事务再根据相同的条件number > 0查询表hero,得到的结果集中包含Session B中的事务新插入的那条记录。
8.Mysql的加锁过程
从图中可知,Mysql Server与InnoDB是一条条数据交互的,修改前需要先读(这里不是指select,而是mysql根据合适的索引读取到需要修改的数据),读的时候加锁,并且锁是一条条加的。
例子:update user set age = 1 where age = 2,由于age上没有索引,mysql查询时使用的是主键索引,mysql server与innodb是一条条数据交互的,全表扫描时,针对表内每一条数据都加了行锁,直到事务结束时才会释放,进而导致了锁全表。
如何修复脏数据?
1. 可以分批次对数据进行修改 2.让where条件必须命中索引,若不命中则必须带有limit
9.行级锁
行锁作用位置:(根据加锁过程可以看到,先查询出数据,再执行锁定,查数据要选择索引)
主键索引
唯一的辅助索引
普通的辅助索引
行锁的3种算法:
record锁:正经的记录锁,锁当前记录
gap锁:间隙锁,锁当前记录(不含)到下一条记录中间(不含)[左开右开],可以防止幻影行的插入
next-key锁:record锁+gap锁,锁当前记录(不含)到下一条记录中间(含)[左开右闭]
10.什么是意向锁
意向锁:Intention Locks
意向共享锁:Intention Shared Lock,IS锁。当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。
意向独占锁:Intention Exclusive Lock,IX锁。当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。
意向锁存在的意义:仅仅为了在之后加表级别的S锁和X锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录(不需要程序员手工上锁)。
————————面试题————————
1) 为什么Mysql默认的隔离级别为Repeatable Read(RR)?
在5.1.5版本,RC隔离级别+STATEMENT的主从同步方式的情况下,会产生主从不一致的Bug,在RR隔离级别时不会,所以默认为RR;
2) 为什么项目使用的隔离识别为Read Commited(RC)?
1.没有必要:绝大部分场景下,不可重复读的问题都可以接受,因为很少会在一个事务里,读取2次相同的数据;且在修改之前,会先执行锁定
2.提高并发:RC 在加锁的过程中,是不需要添加Gap Lock和 Next-Key Lock 的,只对要修改的记录添加Record Lock。
3.减少死锁:RR的事务隔离级别会增加Gap Lock和 Next-Key Lock,这就使得锁的粒度变大,那么就会使得死锁的概率增大。