mysql事务隔离级别

事务特性定义acid

  • 原子性(A):事务是最小单位,不可再分
  • 一致性©:事务要求所有的DML语句操作的时候,必须保证同时成功或者同时失败
  • 隔离性(I):事务A和事务B之间具有隔离性
  • 持久性(D):是事务的保证,事务终结的标志(内存的数据持久到硬盘文件中)

隔离级别

  • 读未提交:read uncommitted
  • 读已提交:read committed
  • 可重复读:repeatable read
  • 串行化:serializable

出现问题

  • 脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
  • 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。强调是读读场景
  • 幻读(Phantom Read):强调的是读写场景,在事物a中查询无记录后准备插入,在插入前b事务先插入并提交,导致a事务插入失败,此场景为幻读。

解决问题


其中mysl innodb引擎场景下通过next-key lock(行锁+gap锁)解决了rr级别的当前读的幻读问题,通过mvcc机制解决了rr级别的快照读的幻读问题


mvcc

是一个通用的解决方案的概念,目的是解决并发下数据库读写性能低下的问题,具体实现看各个厂商,具体到mysql的innoDB引擎中是通过快照读来实现mvcc,实现读写无锁化


快照读

  • 像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
  • 快照读本身也是一个抽象概念,再深入研究。MVCC模型在MySQL中的具体实现则是由 多个隐式字段,undo日志 ,Read View 等去完成的

当前读

像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。


重点解析读已提交rc和可重复读rr


读已提交rc

  • rc使用了mvcc机制,即有快照读,那么为什么依然会出现幻读和不可重复读的问题,这个取决于快照读中read view生成的时机,根据隔离级别的定义,rc下的read view在每一次select语句时都会新生成一个,所以可以读到当前时间点已提交的事务的最新数据,虽然保证了数据是最新的,但是同时带来在一个事务范围内同一条数据的内容发生变化的可能(不可重复读现象),也可能带来数据行数发生增加(幻读现象)
  • rc下的当前读同rr的当前读

可重复读rr

  • rr使用了mvcc机制,即有快照读,那么为什么能解决幻读和不可重复读的问题,这个取决于快照读中read view生成的时机,根据隔离级别的定义,rr下的read view在事务中第一次select语句时会新生成一个,后续查询均采用该read view中的数据,所以可以读到的数据永远是第一次读到的(不同的查询语句当然不同,不予讨论)。虽然保证了数据读出来是不变的,但是依然不能避免其他事务做当前读操作导致的幻读,即rr读到的是旧的副本。算是为了提高并发读写场景下性能作出的牺牲吧,牺牲了一致性。
  • 但是rr在当前读场景下通过next-key lock(行锁+gap锁)解决了幻读问题,白话就是对范围内的数据行进行加锁,禁止其他事务并发写操作,也就不存在幻读问题了。
  • 常见用法场景:某个事务内需要先查询,后进行逻辑处理,最后更新数据库,这种一般先select where xx>a and xx<b for update,这样就锁住了a的上个位置到b的下个位置的数据(gap锁的定义,具体概念和细节需要额外展开讲,推荐看https://www.cnblogs.com/crazylqy/p/7611069.html)。但是缺点显而易见,锁的时间较长,取舍取决于业务。

read view

用来存储事务相关信息,包括事务id等等,具体数据存放在undo log中。


实践

mysql5.6版本
select @@tx_isolation;
select version();
set session tx_isolation='repeatable-read';
BEGIN;
-- select * from sys_role;
update sys_role set REMARK = 'for update3' where id = 22
select * from sys_role where id = 23 for update
COMMIT


参考

https://www.jianshu.com/p/8845ddca3b23
https://www.cnblogs.com/jian-gao/p/10795407.html
https://www.cnblogs.com/JMrLi/p/12705188.html

原文地址:https://www.cnblogs.com/wish5714/p/14953559.html