MySql数据库——事务隔离级别和锁关系学习
引言:
对于事务来说,有四种隔离级别,本文通过对多篇博客的理解和汇总,加上实际的效果展示和个人理解,对MySql四种事务隔离级别和锁进行分析。
一.事务隔离级别:
事务具有ACID属性,而事务的隔离级别可以不同程度的解决事务并发时可能产生的问题,可以根据不同业务逻辑需求,来选择不同的事务隔离等级,事务隔离等级越高,越能保证数据的一致性,但就更趋近于串行化,降低并发性能,导致效率变低。
四种事务隔离级别:
未提交读(RU)
已提交读(RC)
可重复读(RR)
序列化/串行化(Serializable)
这四种隔离级别可以在不同程度上解决事务在并发时产生的问题——脏读,不可重复读,幻读。
1.脏读:有AB两个事务,B事务对一条数据进行修改,但未提交;而A事务在这之后对同一条数据进行读操作,读到的若是未提交的修改后的数据,就说明产生了脏读现象。
2.不可重复读:有一个A事务和若干其他事务,A事务对一条数据(或多条符合同一查询条件的数据)进行多次的读操作;其他的事务对A事务所读的数据进行了修改(这里不包括插入新的数据),若是未提交,在RU级别可以读到修改的数据,若是已提交,在RC级别及以下可以读到修改的数据;那么这种情况就可能会导致A事务在多次读取数据的时候,发现读取的数据值不一致,这种现象被称为不可重复读。
3.幻读:幻读的概念在网上很难找到一个全面且完全正确的解释,这里的解释是我个人的理解,可能会有偏差。
幻读大致上有两种情况:
幻读情况1:同样的查询语句,前后两次读取,发现数据量的个数发生了改变。(RU,RC级别不能解决这个问题,RR和序列化可以解决这个问题)
幻读情况2:第二种情况中还可以分为两种情况一,有AB两个事务,A事务按某个条件查询数据,B事务在A事务查询之后插入了一条符合A事务查询条件的数据,这里B事务提交与否的情况与不可重复读的一致,在B事务做完操作后,A事务也想插入B事务刚才插入的数据,但却发现插入不成功,第一次没有读到的数据,但却插入不成功,这种情况即为幻读;二,有AB两个事务,A事务按某个条件查询数据,B事务在A事务查询之后,删除了一条之前A事务查询到的数据,在B完成操作后,A事务也想把B刚才删除的数据删除掉,但发现影响的行数是0,明明查到了,但删不掉,这种情况也是幻读,并且在RR的隔离级别下,不仅影响行数为0,再查的时候,数据还依然存在,造成这种现象的原因是RR级别使用了快照读(在下文会有解释)。(RR隔离级别可以在一定程度上避免这种情况,序列化可完全避免)
二.锁的概念:
1.按功能划分:
共享锁(读锁):有A事务对某数据加读锁,那么其他事务只可以读这条数据,但不能修改。
排他锁(写锁):有事务A对某数据加写锁,那么只有A事务可以操作此数据,别的事务既不能读,更不能改。
其实还有两种意向锁,但本文内容与意向锁关联不大,暂不作介绍。
2.按范围划分:
行锁:mysql数据库下的InnoDB引擎支持行锁,锁住一行数据。
表锁:mysql数据库下的MyISAM和InnoDB引擎都支持表锁,锁住一整张表。
页锁在这里不做介绍
3.按用途划分:
乐观锁:乐观锁其实从实现的角度上,并没有进行加锁的操作,而是使用版本号,来控制数据的一致性,例如在提交修改的数据时,会判断这条修改的数据的版本号是否大于修改前的版本号,若大于则修改。因为乐观锁没有加锁和解锁的开销,所以从效率上比较可观,但是乐观锁的缺点是不可以跨应用操作,所以就产生了悲观锁的概念。
悲观锁:悲观锁是真正的对需要操作的数据进行了加锁和解锁的操作,来确保事务并发而带来的问题,虽然把数据锁死可以减少错误,但加锁解锁会加大开销,效率会相应下降。
三.事务隔离级别与锁之间的关系:
这部分主要是介绍mysql中四种事务隔离级别是如何实现的,能解决那些并发问题,以及使用了哪些锁来实现这四种隔离界别。
隔离级别 |
脏读 |
不可重复读 |
幻读 |
未提交读(RU) |
可能 |
可能 |
可能 |
已提交度(RC) |
不可能 |
可能 |
可能 |
可重复读(RR) |
不可能 |
不可能 |
可能 |
可序列化(Serializable) |
不可能 |
不可能 |
不可能 |
在开始下面内容之前,需要知道mysq中两种不同的select方式:
快照读:读取的是记录的历史版本,在一个没有结束的事务中,快照读每次读取的都和在本次事务中第一次读到的信息一致(不加读锁)。
当前读:读取的是记录的最新版本(会加读锁)。
1.未提交读(RU):
1.1实现机制:
事务在读数据的时候采用当前读
事务在修改数据的时候加共享锁,提交后释放(解决了修改时,数据被删除或修改的情况)
1.2脏读情况:会发生
首先是开启分别开启两个会话,后续会用A事务和B事务来代指这两个会话,然后把事务隔离级别设置为RU
以下操作按顺序进行:
A事务查询某表信息:
B事务插入新数据,但不提交:
A事务再次查询此表:读到了B事务未提交的操作,脏读情况发生
1.3不可重复读情况:会发生
A事务查询id为30的数据:
B事务对id为30的数据进行修改:提交与否不影响结果
A事务再次查询id为30的数据:与第一次读的num数据值不同,不可重复读情况发生
1.4幻读情况:会发生
A事务查询id<30的数据:
B事务插入一条id<30的数据:提交与否不影响结果
A事务再次查询id<30的数据:查到的数据比第一次多一条,幻读情况1发生
2.已提交读(RC):
2.1实现机制:
事务在读数据的时候加读锁(当前读),读完即释放共享锁
事务在修改某数据时会加上写锁,直到事务结束再释放,这样的机制保证了RC隔离级别不会发生脏读,只有提交过的事务,才能被其他事务看见
2.2脏读情况:不会发生
首先是开启分别开启两个会话,后续会用A事务和B事务来代指这两个会话,然后把事务隔离级别设置为RC。
以下操作按顺序进行:
A事务查询某表信息:
B事务插入新数据,但不提交:
A事务再次查询此表:没有读到B事务插入的数据,脏读情况没有发生
2.3不可重复读情况:会发生
RC隔离级别下的不可重复读的情况和RU是类似的,只是B事务在修改数据的时候,需要提交。
2.4幻读情况:会发生
RC隔离级别下的幻读的情况和RU是类似的,只是B事务在插入数据的时候,需要提交。
3.可重复读(RR):
3.1实现机制:MVCC(Mysql下的InnoDB引擎为例)
事务在读数据的时候采用的是快照读(解决不可重复读问题)
事务在修改数据的时候加写锁,并且Mysql采用了间隙锁,但触发间隙锁的前提是(查询条件列不可以是唯一索引和主键),在触发间隙锁后,会锁住一定范围内的数据,防止在这范围内插入数据,这个机制可以在一定程度上降低发生幻读情况2的可能。
3.2脏读情况:不会发生
首先是开启分别开启两个会话,后续会用A事务和B事务来代指这两个会话,然后把事务隔离级别设置为RR。
RR隔离级别下的脏读的情况和RC一样
3.3不可重复读情况:不会发生
A事务查询id为30的数据:
B事务对id为30的数据进行修改:修改后提交事务
A事务再次查询id为30的数据:与第一次读的num数据值相同,没有发生不可重复读的情况
3.4幻读情况:会发生幻读情况2,不会发生幻读情况1(虽然其他事务仍可能会insert数据,但是由于RR采用了快照读,所以不会读到insert的数据)
幻读情况2:会发生
A事务查询全部数据
B事务插入一条新数据:需要提交事务
A事务由于不知B事务插入过这条数据,所以也想插入这条数据:A事务会插入失败,并且再次全部读数据的时候,还没有发现这条因为主键约束而插入失败的数据,这样的情况即发生了幻读情况2。
幻读情况2:会发生
A事务查询全部数据
B事务删除A事务查询的数据中的一条:需要提交事务
A事务由于不知B事务删除过这条数据,所以也想删除这条数据:A事务删除语句执行后,会发现影响的行数为0,并且再次全部读数据的时候,还发现这条数据没有被删除,这样的情况也是发生了幻读情况2。
3.5间隙锁发生情况:若查询列是上有索引(且不是唯一索引)且不为主键,则间隙锁会触发一定范围的锁,如下面的例子;若查询列上没有索引且不是主键,那么间隙锁会为整张表上锁
A事务查询全部数据:
A事务修改num=30的数据(num是普通索引),把name改为test:
B事务插入一条num=32的数据:可以插入成功
B事务插入一条num=29的数据:发现插入阻塞,造成这种现象的原因就是因为间隙锁的作用,由于A事务以num=30的索引列为条件修改了数据,所以间隙锁把num=30两端的范围数据给锁起来了,即num = (5,30),在这个范围内的数据都被上锁了,注意这里都是开区间,在这个范围外的数据可以被操作。
4.序列化(Serializable):
实现机制:
事务在读取数据时,对整个表加读锁,提交或回滚事务后释放
事务在修改数据时,对整个表加写锁,提交或回滚事务后释放
这是最高的隔离级别,可以解决脏读,不可重复读和幻读,但同时效率也是最差的一个。它解决这些由于事务并发带了的问题的方法就是把这些操作变成串行操作,一旦不符合条件,就会被阻塞,所以效率特别差。
参考博客:
虚拟WORLD-er:https://blog.csdn.net/qq_37960007/article/details/90644635?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-1-90644635.nonecase
三石雨:https://www.cnblogs.com/exceptioneye/p/5450874.html
李俊阳:https://www.cnblogs.com/ljy-skill/p/10622865.html
牛初九:https://www.cnblogs.com/boboooo/p/12370770.html#4506259
Luke:https://zhuanlan.zhihu.com/p/109414420