并发控制的两种方式以及innodb的多版本并发控制

一、控制并发的两种方式

1、加锁。

2、多版本控制。

1、加锁是使用最常见也是最多的方式,简单的加锁,

比如对一份数据:i++(i++线程不安全,); 为了控制并发,把这份数据简单粗暴的加锁了,变成了串行读写,读和读互斥,写和写互斥,读和写互斥,很大的影响了性能。于是,衍生了,读写锁,或者叫共享锁和排它锁,比如ReentrantReadWriteLock读写锁,这个类维护了一个读锁,一个写锁,读读不互斥,写和任何锁互斥,很大的提高了并发量,但是又会存在,如果读并发太高,写操作要等待它前面的所有读锁释放之后才能执行(不管公平策略还是非公平策略都是如此),而且读写还是会和写锁互斥,一旦读锁长期占用锁不释放,会导致写锁以及后面的所有读锁都得不到执行,所以java8出来了个StampedLock,ReentrantReadWriteLock的改进版,StampedLock引入了一个stamp票据的概念,可以有乐观读,乐观读锁锁住之后,返回一个stamp票据,在读过数据之后,验证一下这个票据是否过期,如果在生成stamp和验证stamp过程中,这个数据改了,则stamp就会过期,则我们可以再自旋读数据或者用悲观读锁读数据。所以StampedLock乐观读可以很好解决ReentrantReadWriteLock的问题。

但是加锁,还是属于悲观锁,写锁和读锁还是还是存在堵塞问题。所以,多版本控制可以解决这个问题。

多版本控制的大概思想是:写操作,复制一份数据作为新版本,然后对新版本数据进行写,写完成了之后把指针指向这个新数据,写的过程中,读的请求读原来的旧版本那一份数据。

比如线程安全的lis:CopyOnWriteArrayList。

二、mysql的innodb的多版本并发控制

大概思想是:在进行事务写操作的时候,会复制一份旧版本数据,然后select读的时候,读的都是旧版本数据,然后写操作改变的是新版本数据,事务回滚的时候则用旧版本恢复数据,事务提交的额时候,则删除旧版本,直接使用新版本。整个过程读和写没有堵塞。

具体的实现是:

设计几个东西:

1、redo日志。

每次事务提交,都需要把数据写到磁盘上去,而如此随机写性能差。redo日志就是,先写到redo日志里,然后定期把redo日志的东西写到磁盘上去。这个redo日志和redis的aop日志类似,都是顺序写末尾。

总结就是:随机写优化成顺序写。

2、undo日志

undo日志,在事务未提交前,把事务操作修改前的旧数据保存到undo日志,在事务回滚的时候,用undo日志还原数据。

比如:delete/update的时候,undo日志会保存,delete/update的那条记录,因为恢复的时候需要全部的记录。insert的时候,只会保存id,因为回滚的时候,直接通过id删除就可以了。

3、回滚段

存储undo的地方就是回滚段。

2.4 mysql的多版本控制

mysql的每一个行,都会存储:1、指向回滚段的指针。2、最近修改它的事务。 3、递增的id。

然后对于一条记录,进行了update/delete操作,原来的数据放到了undo日志里面,然后这个事务自己读当然就是读mysql表的数据,其他事务读,则是读回滚段里的旧数据。同时、对于同一条记录,另一个事务再进行写操作就会锁住。

总结:innodb并发高的原因就是:多版本控制,读和写操作不互斥。写数据的同时可以高并发的进行读操作。而读写锁,则写操作的时候不能进行读。

你可能感兴趣的:(并发控制的两种方式以及innodb的多版本并发控制)