Mysql 事务隔离级别
在MySQL的众多存储引擎中,只有InnoDB支持事务,所有这里说的事务隔离级别指的是InnoDB下的事务隔离级别。
一、事务的基本要素(ACID)
- 原子性(Atomicity);事务开始后所有操作,要么全部做完,要么全部不做,不能停滞在中间环节。
- 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏。
- 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。
- 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
二、事务的并发问题
- 脏读:事务B修改数据但未提交,事务A读数据,然后B回滚,则A读到的是脏数据。
- 不可重复读:事务A第一次读取数据,事务B修改数据提交,事务A第二次读数据,两次数据不一致。
- 幻读:事务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