MySQL系列之三 -- -并发(MVCC)

  • MySQL 并发控制如何实现
  • MySQL 如何实现高并发?

一 并发控制

抛开MySQL,通过技术上来讨论并发控制的实现:常见的实现并发控制保证数据一致性的方式

  1. 锁(Lock)
  2. 数据多版本(Mutil Version)

二 锁

2.1 保证一致性方式

使用锁来保证数据的一致性,通常普通的使用如下方式保证一致性:

  1. 数据操作前,获取临界资源的锁,锁住该临界资源,实施互斥,不允许其他的并发操作。
  2. 操作完成后,释放锁,运行其他并发任务进行执行,获取临界资源的锁。

2.2 锁存在的问题

基于内存我们经常使用同步锁机制,但是在数据库层面来说,同步锁的确会存在很大性能问题。普通锁不仅仅针对写任务无法并发执行,就连读任务也无法并发执行。并发上锁针对一致性的实现其实是通过将执行任务串行化来保证的

2.3 并发锁优化

共享锁排它锁

  • 共享锁(Share Locks, S锁):读取数据是加S锁
  • 排它锁(eXclusive Lock,X锁):修改数据是加X锁

原理:

  • 共享锁之间不互斥:即读读可并发执行。
  • 排它锁与任何锁互斥:读写,写写不可以并发执行。

即:写任务没有完成之前,数据是不能被其他任务读取(MySQL:写事务没有提交,相关数据的读操作select也会被阻塞),这对并发也存在一定的影响。
如何让针对写任务没有完成,其他读任务也可以并发执行呢,MySQL的数据多版本就是实现此的方案。

三 MVCC

3.1 mvcc原理

终于引出了本篇的主角:数据多版本,MySQL提供并发读写的方法,简要说下其核心原理:

  1. 写任务来临时,将数据克隆一份,对其进行标注,以版本号进行区分。
  2. 写任务操作克隆的数据,直至数据操作完成,提交事务。
  3. 在期间如有读任务来临,读任务可以继续读取旧版本的数据,并发读任务不会被阻塞。


    mvcc

数据多版本,通过去读旧版本数据能都极大的提供任务的并发度
?1: 多数据版本在MySQL中InnoDB是如何实现呢?

3.2 redo - undo - rollback segment

在介绍MySQL中InnoDB是如何使用MVCCredo - undo - rollback segment

3.2.1 redo Log

数据库提交事务后,必须保证更新后的数据刷到磁盘,以保证事务的ACID特性。磁盘的随机读写性能比较低,每次写数据都需要同步刷入磁盘,这将会极大的影响数据库的并发吞吐能力。

redo Log就是来优化上述情况:redo日志用于保障,已提交事务的ACID特性。

  • redo Log 将数据的修改操作先写入redo log文件里(其实就是将随机写变成顺序写),在定期将数据刷到磁盘,来提高并发性能。
  • redo Log 用于保障已提交的事务的ACID特性。数据库崩溃,还没来得及刷盘的数据,在数据库重启后,会重做redo日志里的内容,以保证已提交事务对数据产生的影响都刷到磁盘上。

3.2.2 undo log

数据库事务未提交之前,会将事务修改的数据镜像(即修改前的旧版本)放入到undo日志里,当事务回滚时,或者数据库奔溃时,可以利用undo 日志(即旧版本数据)撤销未提交事务对数据产生的影响。

1. insert 操作:undo日志记录新记录的PK(ROW_ID),回滚是直接删除。
2. delete/update 操作:undo日志记录旧数据的row, 回滚是直接恢复。
两者分别存在不同的buffer里面
  • undo日志用于保障,未提交事务不会对数据库的ACID特性产生影响。

3.3.3 rollback segment

存储undo日志的地方就是回滚段。undo日志和回滚段与InnoDB的MVCC密切相关。
即:旧版本数据存储在回滚段里,即历史数据的快照

  1. 没有未提交的事务,回滚段是空的
  2. 如果开启一个事务,且没有提交,此时对应的操作旧版本数据则会进入回滚段
  3. 如果事务进行rollback,此时可通过回滚段的undo 日志进行回滚。
  4. 如果事务提交,则删除回滚段里的undo日志。

3.3 MVCC 多版本并发控制

多版本并发控制(Multi Version Concurrency Control, MVCC)。行锁,并发,事务回滚等多种特性都和MVCC相关。
MVCC就是通过“读取旧版本数据”来降低并发事务的锁冲突,提高任务的并发度。

  • 回滚段里的数据,其实是历史数据的快照(snapshot),这些数据是不会被修改,select可以肆无忌惮的并发读取他们。
  • 快照读(Snapshot Read),这种一致性不加锁的读(Consistent Nonlocking Read),就是InnoDB并发如此之高的核心原因之一。
  • 这里的一致性是指,事务读取到的数据,要么是事务开始前就已经存在的数据(当然,是其他已提交事务产生的),要么是事务自身插入或者修改的数据。
  • InnoDB所有普通select都是快照读;

题外音:MVCC 是在RR 与 RC级别下的

3.4 MVCC 举例

  • 读提交(RC),可重复读(RR)两个不同的事务的隔离级别下,快照读有什么不同呢?
    • RC:解决脏读,读取数据必是已提交事务写入的,但可能存在幻影行。同一个事务里,连续相同的read可能读到不同的结果集。
    • RR:不仅解决脏读,还解决了幻影行问题。同一个事务里,连续相同的read读到相同的结果集。

答案:

  1. 事务总能够读取到自己写入(insert/update/delete)的行记录。
  2. RC级别下,快照读总是能够读到最新的行数据快照(必须是已提交事务写入的数据)。
  3. RR级别下,能够读到读事务首次read的记录时间T之前提交的事务写入的数据,之后读不会读取到T时间后已提交事务写入的数据,保证同一个事务连续读到的相同的结果集。
table T (int id PK, varchar name);
存在三条记录:
1,张三
2,王五
3,李四

Case1 : 两个并发事务A,B执行的时间序列如下(A先于B开始,B先于A结束)

A1: start transaction;
         B1: start transaction;
A2: select * from t;
         B2: insert into t values (4, xiongda);
A3: select * from t;
         B3: commit;
A4: select * from t;

假设:RC,结论:
1. A2 读物的结果集为:{1,2,3};
2. A3 读物的结果集为:{1,2,3},原因:B事务未提交;
3. A4 读物的结果集为:{1,2,3,4},原因:B事务已提交;

假设:RR,结论:
1. A2 读物的结果集为:{1,2,3};
2. A3 读物的结果集为:{1,2,3},原因:B事务未提交;
3. A4 读物的结果集为:{1,2,3},原因:B事务提交时间在A2第一个读之后;

Case2 : 两个并发事务A,B执行的时间序列如下(B先于A开始,B先于A结束)

         B1: start transaction;
A1: start transaction;
A2: select * from t;
         B2: insert into t values (4, xiongda);
A3: select * from t;
         B3: commit;
A4: select * from t;

事务的开始时间不一样,不会影响“快照读”的结果,所以结果集和case 1一样。
假设:RC,结论:
1. A2 读物的结果集为:{1,2,3};
2. A3 读物的结果集为:{1,2,3},原因:B事务未提交;
3. A4 读物的结果集为:{1,2,3,4},原因:B事务已提交;

假设:RR,结论:
1. A2 读物的结果集为:{1,2,3};
2. A3 读物的结果集为:{1,2,3},原因:B事务未提交;
3. A4 读物的结果集为:{1,2,3},原因:B事务提交时间在A2第一个读之后;

结论

事务的开始时间不一样,不会影响“快照读”的结果。

  • RC下,事务在每次Read操作时,都会建立Read View
  • RR下,事务在第一个Read操作时,会建立Read View

你可能感兴趣的:(MySQL系列之三 -- -并发(MVCC))