- CAS 实现:Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
- 版本号控制(MVCC):一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会 +1。
mysql之事务、锁、隔离级别与MVCC
参文章
《【MySQL】当前读、快照读、MVCC》
《正确的理解MySQL的MVCC及实现原理》
《MySQL 8.0 MVCC 源码解析》
《mysql幻读》
《细谈数据库表锁和行锁》
《什么是乐观锁,什么是悲观锁》
《MySQL事务和隔离》
写在开头:本文为学习后的总结,可能有不到位的地方,错误的地方,欢迎各位指正。
一、 一段连续的不可拆分的业务逻辑的组合称为事务
举例,比如银行转账,A的账户-50,B的账务+50,那么这2个操作必须都完成才行,称为事务
基于上面的例子,事务的基本属性A(原子性)C(一致性)I(隔离性)D(持久性)
二、事务的隔离级别分为
(1) 读未提交
会出现脏读,会读取到其他事务未提交的数据
(2)读已提交
事务A的2个查询期间事务B对一条数据做了修改,导致2次数据的读取不一致。
解决方案是加锁,表锁、行锁都可以,行锁颗粒度小,执行开销大,好处是并发度大
(3)可重复读(mysql的默认隔离级别)
当前读模式下,因为加的是行锁,因此没办法防止住insert语句,导致2次
查询的语句不一致。对于快照读,使用MVCC解决,对于当前读,使用间隙锁解决
(4)串行化
这是最高的隔离级别,它强制事务都是串行执行的,使之不可能相互冲突,从而解决幻读问题。
换言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
三、 锁
从概念上分有2种锁,悲观锁与乐观锁
悲观锁,是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,
因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。悲观锁的实现:
1. 传统的关系型数据库使用这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。
2. Java 里面的同步 synchronized 关键字的实现。
悲观锁主要分为共享锁和排他锁:
共享锁【shared locks】又称为读锁,简称 S 锁。顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,
都能访问到数据,但是只能读不能修改。
排他锁【exclusive locks】又称为写锁,简称 X 锁。顾名思义,排他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,
其他事务就不能再获取该行的其他锁,包括共享锁和排他锁。获取排他锁的事务可以对数据行读取和修改。
行锁、表锁是共享锁、排他锁的执行粒度。
1、表锁(解锁语句 UNLOCK TABLES)
(1)读锁
lock table table_name read
对该表加读锁后,自己也不能对其进行修改;自己和其他线程只能读取该表
(2)写锁
lock table table_name write
当对某个表执加上写锁后,该线程可以对这个表进行读写,其他线程对该表的读和写都受到阻塞
2、行锁
行锁是在引擎层由各个引擎自己实现的,有的引擎并不支持行锁,比如MyISAM就不支持行锁,这意味着:
并发控制只能使用表锁,对于这种引擎(MyISAM)的表,同一张表上任何时刻只能有一个更新在执行,这严重影响了并发度;
InnoDB是支持行锁的,这也是MyISAM被InnoDB代替的主要原因;
首先注意:InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则都会从行锁升级为表锁
update table1 set bbb='2' where aaa='1'
事务开启后,事务A更新table1的1行,但未提交,则事务B中无法更新这一行(会阻塞,事务A提交后才行,阻塞一段时间后
还未获得行锁则会自动放弃更新),但可以更新其他行。
需注意,上面这条语句改成update table1 set bbb='2' where ccc='1' 则不会加行锁,但是事务B依然会阻塞,
原因是行锁降级成了表锁,因此事务B依然无法update(参考上文的写锁)。
在InnoDB事物中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事物提交了才会释放,这个就是两阶段锁协议。
3间隙锁
首先要了解下幻读这个问题,我们开启事务A后读取一条sql得到了10行数据,此时事务B无法对这10行做update、delete操作,
但是此时使用的是行锁,也就是锁的粒度是加在每个数据行上的,这也就意味着我们可以向这些数据中进行插入操作,
比如事务B向这张表插入了1条数据,导致事务A再次查询时得到了11条记录,这就是幻读。mysql的应对方案是增加间隙锁。
将两行记录间的空隙加上锁,阻止新记录的插入;这个锁称为间隙锁。
悲观锁的特性:
悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,
处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,
一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。
乐观锁采取了更加宽松的加锁机制。也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,
但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。乐观锁的实现: