Mysql 事务隔离级别

在MySQL的众多存储引擎中,只有InnoDB支持事务,所有这里说的事务隔离级别指的是InnoDB下的事务隔离级别。

一、事务的基本要素(ACID)

  1. 原子性(Atomicity);事务开始后所有操作,要么全部做完,要么全部不做,不能停滞在中间环节。
  2. 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏。
  3. 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。
  4. 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

二、事务的并发问题

  1. 脏读:事务B修改数据但未提交,事务A读数据,然后B回滚,则A读到的是脏数据。
  2. 不可重复读:事务A第一次读取数据,事务B修改数据提交,事务A第二次读数据,两次数据不一致。
  3. 幻读:事务A update表的全部行,事务B插入一行,事务A就会发现表中还有未修改的行。(一般加间隙锁)

三、MySQL事务隔离级别

事务隔离级别 脏读 不可重复读 幻读
读未提交 会 
读已提交 不会
可重复读 不会 不会
串行话 不会 不会 不会

查看mysql的默认事务隔离级别“show global variables like ‘tx_isolation’; ”

数据库的锁是加在数据行对应的索引上的

 =======================悲观锁方向=========================================

四、InnoDB 行锁模式

Innodb的行锁模式有以下几种:共享锁,排他锁,意向共享锁(表锁),意向排他锁(表锁),间隙锁。

1.共享锁

又称读锁,读取操作创建的锁。一旦上锁,任何事物无法对其修改,但可以并发读取数据,也可以对此数据再加共享锁。

2.排他锁

又称写锁,如果事务对数据A加上排他锁后,则其他事物不可并发读取数据,也不能再对A加任何类型的锁。获准排他锁的事务既能读取数据,又能修改数据。

在MySQL InnoDB中,UPDATE/INSERT/DELETE操作都会自动加排他锁,普通的SELECT语句不会加任何锁,如果想加锁,可以使用下面方式

SELECT * FROM table_name WHERE id = 1 LOCK IN SHARE MODE; -- 显式加共享锁
SELECT * FROM table_name WHERE id = 1 FOR UPDATE;         -- 显式加排他锁

3.意向锁 (数据库自动帮加)

InnoDB为了让表锁和行锁共存而使用了意向锁。(意向锁均为表级锁)

事务A既然锁住了某一行,其他事务就不可能修改这一行。这与”事务B锁住整个表就能修改表中的任意一行“形成了冲突。所以,没有意向锁的时候,行锁与表锁共存就会存在问题!

意向共享锁:表示事务准备给数据行加入共享锁(行读锁S), 先加意向共享锁IS

意向排他锁:事务准备给数据行加入排他锁(行写锁X),先加意向排他锁IX

事务A在申请行写锁之前,数据库会自动给事务A申请表的意向排他锁,当事务B申请表写锁时,因为表上已经有意向排他锁,所以B申请的写锁会被阻塞。

 意向锁相互兼容,因为IX、IS只是表明申请更低层次级别元素(比如 page、记录)的X、S操作

 4.记录锁、间隙锁、临键锁、意向插入锁

=================================乐观部分===============================

MVCC

Multi-Version Concurrency Control 即多版本并发控制,是为了解决读-写冲突而出现的。

在MySQL中,多版本并发控制是InnoDB存储引擎实现读已提交和读未提交,隔离级别的具体方式。读未提交总是读取最新数据行,无需使用MVCC;可串行化需要对所有读取的行都加锁,单纯使用MVCC无法实现。

MVCC一般用两种实现方式,InnoDB采用的是后者

  • 实时保留数据的一个或者多个历史版本
  • 在需要时通过undo日志构造出历史版本

InnoDB为每个记录行都实现了三个隐藏字段

6字节的事务ID(DATA_TRX_ID)标记了最新更新这条记录的事务id

7字节的回滚指针  (RAR_ROLL_PTR)指向当前及录像的rollback segment 的undo log记录,找之前版本的数据就是通过这个指针

一个6字节的DB_ROW_ID字段包含一个行ID,当插入新行时,该行ID会单调增加。如果 InnoDB自动生成聚簇索引,索引包含行ID值。否则,该 DB_ROW_ID列不会出现在任何索引中。

另外,每条记录的头信息(record header)里都有一个专门的bit(deleted_flag来表示当前记录是否已经被删除

 

 

更新字段时会进行如下操作

用排他锁锁定该行

记录redo log

把该行修改前的值复制到undolog,即上图中下面的行。。

修改当前行的值,填写事务编号,回滚指针指向undo log刚刚copy的行

select 

  • InnoDB只查找版本号小于等于当前事务版本的数据行,这样可以确保数据行要么是在开始之前已经存在 了,要么是本事务自身插入或修改过的
  • 行的删除版本号 要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行,在事务开始之前未被删除。

insert  InnoDB为新插入的每一行保存当前事务版本号作为事务ID

delete  InnoDB为删除的每一行保存当前事务版本号作为事务ID

update InnoDB为插入一行新纪录,保存当前系统版本号作为行版本号, 同事保存当前系统版本号到原来的行作为行删除标识符

 

对于READ_COMMITTED 

读提交是,读事务每次都读取undo log中最近的版本,因此每次都能读取到最新的数据。

MySQL的读一致性,是通过一个叫read view的结果来实现的。

readview中维护了系统中活跃事务集合的快照。详见https://yq.aliyun.com/articles/560506

posted @ 2019-04-04 23:02  茶饭不撕  阅读(1783)  评论(0编辑  收藏  举报