1. 为什么事务存在隔离级别
丢失更新:多个事务基于最初选定的值更新同一行,由于每个事务都不知道其他事务的存在,就会发生丢失更新的情况。最后更新的值覆盖其他事务所做的更新。
脏读:一个事务读取到另一个事务未提交的数据;
不可重复读:同一个事务中读取某些数据已经发生改变,或某些记录已经删除。
幻读:一个事务按照相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足查询条件的新数据,这种现象被称为幻读。
以上是并发事务过程中会存在的问题,解决更新丢失可以交由应用,但是后面3种情况需要数据库提供事务间的隔离机制来解决。
实现隔离机制的方案:
- 加读写锁;
- 一致性快照读,即MVCC;
本质上,隔离级别是一种并发性能和并发产生副作用间的妥协。
2. 事务的隔离级别
1. READ-UNCOMMITTED(读未提交)
读未提交RU,在该隔离级别下会读到未提交事务所产生的数据更改,这意味着会读到脏数据。
2. READ-COMMITTED(读已提交)
读已提交RC,在这一隔离级别下,可以在SQL级别做到一致性读,每次SQL语句都会产生新的ReadView。这就意味着两次查询之间有别的事务提交了,是可以读到不一致的数据的。
3. REPEATABLE-READ(可重复读)
可重复读RR,在第一次创建ReadView后(例如事务执行的第一条SELECT语句),这个ReadView就会一直维持到事务结束,也就是说,在事务执行期间可见性不会发生变化,从而实现了事务内的可重复读。
4. SERIALIZABLE(序列化)
序列化的隔离级别是最高等级的隔离级别,即将快照读升级为当前读(使用共享锁),实现读写互斥。
3. mysql如何实现隔离级别
- 对于
RU
隔离级别,直接读取版本最新内容即可。 - 对于
SERIALIZABLE
隔离级别,则是通过加互斥锁来实现。 - 对于
RC
和RR
,则是依赖MVCC来实现。
3.1 MVCC实现原理
MVCC使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,而是把数据库的行锁和行的版本号结合起来,只需要很小的开销,就可以实现非锁定读。从而提高数据库的并发性能。
2.1 MVCC实现原理
InnoDB 中 MVCC 的实现方式为:每一行记录都有两个隐藏列:DATA_TRX_ID
(创建版本号)、DATA_ROLL_PTR
(回滚指针)(如果没有主键,则还会多一个隐藏的主键列)。
2.1.1 名词说明
DATA_TRX_ID:记录最近更新这条行记录的事务 ID,大小为 6 个字节;
DATA_ROLL_PTR:指向回滚段的指针,InnoDB 便是通过这个指针找到之前版本的数据。该行记录上所有旧版本,在 undo 中都通过链表的形式组织。
2.1.3 组装undo log链
多个事务并行操作某行数据的情况下,不同事务对该行数据的UPDATE会产生多个版本,然后通过DATA_ROLL_PTR(回滚指针)
组装成Undo log链。
insert操作:
会产生一条新记录,它的创建版本号为事务id;
delete操作:
delete可看做特殊的update,其实是软删,真正执行删除操作会在 commit 时。
update操作:
- 把该行原本的值拷贝到undo log,创建版本号和回滚指针都不动;
- 修改该行的值,更新创建版本号为当前事务id,将回滚指针指向拷贝到undo log链中的旧版本记录。
select操作:
- 删除版本号>当前事务版本号,说明删除操作是在当前事务启动后做的。
- 创建版本号<=当前事务版本号,说明插入操作时在事务启动前或者事务中做的。
4. MVCC如何实现可重复读
RC和RR均使用了MVCC技术,RC、RR 两种隔离级别的事务在执行普通的读操作时,通过访问undo log
的方法,使得事务间的读写操作得以并发执行,从而提升系统性能。RC、RR 这两个隔离级别的一个很大不同就是生成 ReadView(快照)的时间点不同。
- RC 在每一次 SELECT 语句前都会生成一个 ReadView,事务期间会更新,因此在其他事务提交前后所得到的
活跃事务id
列表可能发生变化,使得先前不可见的版本后续又突然可见了。 - RR 只在事务的第一个 SELECT 语句时生成一个 ReadView,事务操作期间不更新。
5. MVCC能否解决幻读
单纯依赖RR级别的MVCC是无法解决幻读的。
场景:一个事务负责观察,一个事务负责修改插入然后提交,然后再另一个事务里面执行更新操作后再查看,幻读依旧会出现。
6. mysql为什么是RR的隔离级别
在Oracle,SqlServer中都是选择读已提交(Read Commited)作为默认的隔离级别,为什么Mysql不选择读已提交(Read Commited)作为默认隔离级别,而选择可重复读(Repeatable Read)作为默认的隔离级别呢?
mysql数据库的主从复制依靠的是binlog。而在mysql5.0之前,binlog模式只有statement格式。这种模式的特点:binlog的记录顺序是按照数据库事务commit顺序为顺序的。
那么这和mysql默认隔离级别是RR有什么关系呢?
原来在RC级别下使用statement是有bug的!
在RC隔离级别下,mysql是没有next-key lock的(即没有间隙锁)当master库有这么两个事务:
- 事务a先delete id<6,然后在commit前;
- 事务b直接insert id=3,并且完成commit;
- 事务a进行commit;
此时binlog记录的日志是:事务b先执行,事务a在执行(binlog记录的是commit顺序)
那么主库此时表里面有id=3的记录,但是从库是先插入再删除,从库里面是没有记录的。
这就导致了主从数据不一致。
为了解决这个问题,故只能使用RR级别,当事务a执行时,使用间隙锁,阻塞事务b执行。
所以Mysql默认的是RR隔离级别。
不用解决,这个问题是可以接受的!毕竟你数据都已经提交了,读出来本身就没有太大问题!Oracle的默认隔离级别就是RC,你们改过Oracle的默认隔离级别么?
OK,在该隔离级别下,用的binlog为row格式,是基于行的复制!Innodb的创始人也是建议binlog使用该格式!
推荐阅读
https://www.cnblogs.com/loleina/p/10344742.html
https://www.cnblogs.com/shoshana-kong/p/10516404.html
https://www.cnblogs.com/zengkefu/p/5678361.html
历史文章
mybatis&&数据库优化&&缓存目录