
目录
一、事务
事务指满足 ACID 特性的一组操作,可以通过Commit
提交一个事务,也可以通过Rollback
进行回滚。

ACID
1. Atomicity 原子性
事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
使用回滚日志
Undo Log
保证原子性
2. Consistency 一致性
数据库在事务执行前后都保持一致性状态,所有事务对一个数据的读取结果都是相同的。
3. Isolation 隔离性
一个事务所做的修改在最终提交以前,对其他事务不可见。
4. Durability 持久性
一旦事务提交,则其所做的修改将会永远保存到数据库中,即使系统发生崩溃,事务的执行结果也不能丢失。
使用重做日志
Redo Log
保证持久性
ACID 之间的关系

- 只有满足一致性,事务的执行结果才是正确的
- 无并发的情况下,事务串行执行,隔离性一定满足。此时只要满足原子性,就可以满足一致性
- 有并发的情况下,多个事务并行执行,事务不仅要满足原子性,还要满足隔离性,才能满足一致性
- 事务满足持久性是为了应对数据库崩溃的情况
AUTOCOMMIT
MySQL 默认采用自动提交模式,如果不显式使用START TRANSACTION
语句来开始一个事务,那么每个查询都会被当作一个事务自动提交。
二、并发一致性
在并发环境下,事务的隔离性很难保证,有可能出现很多并发一致性问题。
丢失修改
T2
的修改覆盖了T1
的修改:

读脏数据
T1
修改一个数据,T2
随后读取这个数据,如果T1
撤销了这次修改,那么T2
读取的数据就是脏数据:

不可重复读
T2
读取一个数据,T1
对该数据进行了修改。如果T2
再次读取这个数据,结果会和第一次读取的结果不同:

幻影读
T1
读取某个范围的数据,T2
在这个范围内插入新的数据。T1
再次读取这个范围的数据,结果会和第一次读不同:

总结
- 主要原因:破坏了事务的隔离性
- 解决方法:通过并发控制来保证隔离性
- 如何实现:通过封锁,但需要用户自己控制,相当复杂
- 数据库管理系统提供了事务的隔离级别,可以让用户轻松的处理并发一致性问题
三、封锁
封锁粒度
MySQL
中提供了两种封锁粒度:行级锁以及表级锁。
应该尽量只锁定需要修改的那部分数据。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、检查锁状态)都会增加系统开销。因此封锁粒度越小,系统开销就越大。
在选择锁粒度时,需要在锁开销和并发程度之间做一个权衡。
封锁类型
1. 读写锁
- 排它锁(Exclusive):简写为
X
锁,又称为写锁 - 共享锁(Shared):简写为
S
锁,又称为读锁
2. 意向锁
使用意向锁(Intention Locks)可以更容易的支持多粒度封锁。
意向锁在原来的X/S
锁之上引入了IX/IS
,都是表级锁,用来表示一个事务想要在表中的某个数据行上加X
锁或S
**锁。有以下两个规定:
- 一个事务在获得某个数据行对象的
S
锁之前,必须获得表的IS
锁或更强的锁 - 一个事务在获得某个数据行对象的
X
锁之前,必须先获得表的IX
锁
封锁协议
1. 三级封锁协议
一级封锁协议
事务T
要修改数据A
时必须加X
锁,直到T
结束才释放锁.
可以解决丢失修改的问题,因为不能同时有两个事务对同一个数据进行修改,那么事务就不会被覆盖
二级封锁协议
在一级的基础上,要求读取数据A
时必须加S
锁,读取完马上释放S
锁。
可以解决读脏数据的问题,因为如果一个事务在对数据
A
进行修改,根据一级封锁协议,会加X
锁,那么就不能再加S
锁了,也就不会读入脏数据
三级封锁协议
在二级的基础上,要求读取数据A
时必须加S
锁,直到事务结束了才能释放S
锁。
可以解决不可重复读的问题,因为读
A
时,其他事务不能对A
加X
锁,从而避免了在读的期间数据发生改变
2. 两段锁协议
加锁和解锁分为两个阶段进行。
可串行化调度是指,通过并发控制,使得并发执行的事务结果串行执行的事务结果相同**。
事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议,是可串行化调度:
lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)
但不是必要条件,例如以下操作不满足两段锁协议,但它仍是可串行化调度:
lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)
MySQL 隐式与显式锁定
MySQL
的InnoDB
存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。
InnoDB
也支持使用特定的语句进行显式锁定:
SELECT ... LOCK In SHARE MODE;
SELECT ... FOR UPDATE;
四、隔离级别
未提交读 READ UNCOMMITTED
事务中的修改,即使没有提交,对其他事务也是可见的
提交读 READ COMMITED
一个事务只能读取已经提交事务所做的修改。也就是说,一个事务所做的修改在提交之前对其他事务是不可见的
可重复读 REPEATABLE READ
保证在同一个事务中多次读取同样数据的结果是一样的
可串行化 SERIALIZABLE
强制事务串行执行
五、多版本并发控制
多版本并发控制(Multi-Version Concurrency Control,MVCC)是MySQL
的InnoDB
存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。
- 未提交读隔离级别总是读取最新的数据行,无需使用 MVCC
- 可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现
版本号
- 系统版本号:是一个递增的数字,每开始一个新事务,系统版本号就会自动递增
- 事务版本号:事务开始时的系统版本号
隐藏的列
MVVC 在每行记录后都保存着两个隐藏的列,用来存储两个版本号:
- 创建版本号:表示创建一个数据行的快照时的系统版本号
- 删除版本号:如果该快照的删除版本号大于当前事务的版本号,则该快照有效,否则表示该快照已经被删除了
Undo 日志
MVCC 使用到的快照存储在Undo
日志中,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来。
更新中…