最近公司的技术分享一直在分享 mysql 事务,隔离级别,锁和 MVCC
方面的知识,但是都是基于单个点进行分析,没有一步一步来阐述,所以在这里我整理了一下 mysql 由 ACID 到 MVCC 这一块的知识准备分享给大家。
咱们开门见山先谈 事务
- 首先思考事务有什么作用?
- 事务是为了保证数据库中数据的完整性和一致性
- 事务的四个基本要素 ACID ?这四要素是怎么实现的?
- 原子性
Atomicity
- 原子性通过 Innodb
undo log
即回滚日志实现
- 原子性通过 Innodb
- 一致性
Consistency
- 一致性通过
lock
锁来实现
- 一致性通过
- 隔离性
Isolation
- 隔离性通过
lock
锁和MVCC
来实现
- 隔离性通过
- 持久性
Durability
- 持久性通过 Innodb
redo log
即重写日志实现 ,和doublewrite
两次写技术来实现
- 持久性通过 Innodb
- 原子性
现在我们开始谈 隔离级别
- 什么是隔离级别 ?
- 并发事务之间相互影响的程度,隔离级别越高影响程度就越低
- 并发事务会造成的问题
- 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
- 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
- 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读
- 隔离级别的种类
read-uncommitted
读未提交- 读数据时不加锁,写数据时加行级共享锁
read-committed
读已提交- 读数据使用
MVCC
,写数据加行级排它锁
- 读数据使用
repeatable-read
可重复读- 读数据使用
MVCC
,写数据加行级排它锁和gap、next-key lock
- 如果是用到了主键的话加锁
- 读数据使用
serializable
串行化- 读数据加表级共享锁,写数据加表级排它锁
在 Mysql 的并发控制中我们常常需要使用到 锁 和 MVCC,那么这两者的使用场景有什么区别呢,为什么有了锁还要引入 MVCC 呢?下面我将为大家娓娓道来这两者使用上的区别
基于锁的并发控制
LBCC
(Lock-Based Concurrent Control) 即基于锁的并发控制- Mysql 的锁由 共享锁(读锁) 和 排它锁(写锁) 组成
- 按粒度也可以分为表锁和行锁
- 加锁和解锁遵循
2PL
即 两阶段锁原则 2PL
就是将加锁/解锁分为两个完全不相交的阶段。加锁阶段:只加锁,不放锁。解锁阶段:只放锁,不加锁- 若所有事务均遵守两段锁协议,则这些事务的所有交叉调度都是可串行化1的
多版本的并发控制
MVCC
即 (Multi-Version Concurrency Control) 多版本的并发控制协议MVCC
最大的好处可以概括为读不加锁,读写不冲突MVCC
并发控制中,读操作可以分成两类:快照读(snapshot read)
与当前读(current read)
。- 快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。
- 简单的select操作,属于快照读,不加锁。
SELECT * FROM user WHERE ?
- 简单的select操作,属于快照读,不加锁。
- 当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录
- 特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
- 特殊读 (加锁读)
SELECT * FROM user WHERE ? LOCK IN SHARE MODE;
- 插入/更新/删除
SELECT * FROM user WHERE ? FOR UPDATE;
INSERT INTO user VALUES (…);
UPDATE user SET ? WHERE ?;
DELETE FROM user WHERE ?;
- 特殊读 (加锁读)
- 特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
- 快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。
当且仅当某组并发事务的交叉调度产生的结果和这些事务的某一串行调度的结果相同,则称这个交叉调度是可串行化 ↩︎