关于Mysql并发理论的一些思考

并发的概念:

数据安全:而所谓数据安全,就是多个线程对同一条数据进行读写操作时,保证该数据被正确修改(修改的结果正确);该数据被正确读取(读取到正确的值)。

并发效率:一个线程在执行任务的过程中,耗费的时间越少越好,而耗费的时间长短的决定因素有些时候在于:锁等待的时间长短,所以把发生锁等待的机率降到最低,把锁等待时间降到最低。

并发的本质就是:以数据安全为前提,想办法提高并发效率。

假设没有事务的场景:

A线程和B线程对Mysql中的同一条数据进行操作:

1、如果是两个线程都去读取同一条数据,那么彼此之间没有任何影响;

2、如何两个线程都去写一条数据,那么Mysql会对该条数据进行加锁,两个线程之间要进行同步,也就是说:一个写完,另一个再写;

3、如果A线程去写这条数据,B线程去读这条数据,则根据B线程的读种类进行分别处理:

  • 如果B线程的读是快照读,那么A线程的写对B线程的读没有任何影响,但是B线程读取的是快照数据,也就是旧的数据(严禁以快照读结果在程序中运算,然后再更新到数据库!!!);
  • 如果B线程的读是共享读(select *** lock in share mode),那么B线程会对读取的数据加读锁,此时A线程要对写的数据加写锁,写锁与读锁是互斥的,A线程的写与B线程的读要进行同步,也就是说:A写完B再读,或B读完A再写;
  • 如果B线程的读是互斥读(select *** for update),那么B线程会对读取的数据加写锁,写锁与写锁之间是互斥的,A线程的写与B线程的读也要进行同步。

假设有事务的场景:

基础知识:

事务的两阶段加锁协议:

一个事务中有多个需要加锁的操作,那么会在分别执行到加锁的语句时进行加锁,等到事务提交之后,才会把这个事务中所有加的锁进行释放。

事务里面的快照读:

一个事务进入Mysql之后会拿到一个全局唯一递增的事务ID,比如A事务进入Mysql拿到的事务ID为35,那么A事务中的快照读,只会读取到数据版本链中小于等于35的最大版本数据。

事务里面的共享读:

一个事务里面的共享读,会去获取该条数据版本链中的最新版本数据,而且会对最新版本数据加读锁(读锁与读锁之间不会互斥,而读写与写锁、写锁与写锁之间会互斥)

事务里面的互斥读:

一个事务里面的互斥读,会去读取该条数据版本链中的最新版本数据,而且会对要读取的最新版本数据加写锁(读锁与读锁之间不会互斥,而读写与写锁、写锁与写锁之间会互斥)

场景设想:

假设一个事务一定是在一个线程里面的,也就是说一个事务里面的所有操作都是由一个线程来完成的。

这里把所有操作都看为在事务中的,即使只有一个操作,那么也把这个操作看为是在一个事务中。

每一个进入Mysql的事务都会拿到一个全局唯一递增事务ID。Mysql会准备一个队列之类的东西,事务只能一个一个进,这样每个事务进入Mysql的时候,就会被分配一个全局唯一递增的事务ID。

假设多个事务同时访问Mysql,这多个事务里面都是不需要加锁的操作(快照读),那么这多个事务对应的多条线程之间就不会发生同步,并发效率很高。

或者是多个事务里面有需要加锁的操作,不过加的都是读锁(比如共享读),此时也没有问题,读锁与读锁之间是不会发生互斥的,并发效率依然很高。

如果多个事务里面有需要加写锁的操作(互斥读、写操作),如果刚好没有对同一条数据进行加写锁,那么也不会产生互斥,这多个事务对应的多个线程之间就不会发生同步,并发效率依然很高。

但如果刚好多个事务里面的几个事务对同一条数据都需要加写锁,此时这几个事务对应的线程就要发生同步了,会影响并发效率。

Mysql提高并发效率的努力方向:

缩小锁的数据范围:将锁的范围缩小到了一条数据的范围,也就是行锁。其思想类似于ConcurrentHashMap里的分段锁思想。

缩小锁的操作范围:将需要加锁的操作缩小到了:写操作、互斥读、共享读。

锁种类划分:划分出了读锁与写锁,读锁与读锁之间是不会产生互斥的。

缩小锁的时间范围:如果一个事务里面最开始就进行了加锁操作,而锁的释放是要等待事务结束才会发生,也就是说该事务持有锁的时间变长了。所以要缩小锁的时间范围就是要:在一个事务里面尽量将需要加锁的操作放在后面。

Mysql的快照读:

按照通常的线程安全理论来讲,就是要在共享变量在被多个线程并发读写时,这个变量可以被“正确地读出来”,而所谓“正确地读出来”就是说读到最新的那个值。

比如CopyOnWriteArrayList,它的get操作是没有加锁的,但通过里面的被volatile修饰的变量array,当A线程给array重新赋值的时间点之后,B线程的第一个读操作就会读取到array新的指向里的数据。

再比如ConcurrentHashMap,它的get操作是没有加锁的,但是它的node里的val和next都是被volatile所修饰的。这就保证了,如果其他线程对某个node的val或next进行了重新赋值,get操作会立刻感知到,并读取到最新的数据。

但是Mysql的快照读并不是这样,事务中的快照读读取到的是一个旧的快照值。也就是说并没有被“正确读出来”。Mysql这样做的目的有两个,第一个:将快照读彻底从锁竞争的游戏中抛出去,提高并发效率;第二个:其实就是为了保证事务里面数据的一致性,即第一次读和第二次读的结果是一致的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值