写点什么

小白必看!结合实际实例,理解事务,多线程面试题 java

用户头像
极客good
关注
发布于: 刚刚

mysql 的事务隔离级别默认是可重复读(REPEATABLE-READ) 我们在使用 mysql 的默认事务隔离级别下,进行举例。大家暂时可以不去深究隔离级别,细节的东西我会在第四节实战进行举例,这里只是为了让大家看到行锁这个现象。



可以看到,在可重复读的隔离级别下,两个事务同时对同一行数据发起修改,后修改事务的会被前面一行修改但是还没提交的事务阻塞,直到前一个事务提及成功,或者是后一个事务等待超时,这个阻塞才会断开。请注意,这里是同一行,也就是说,后面发起的事务可以对该表的其他行进行修改,而不会被阻塞。



这里,我留给你一个问题,在可重复读的隔离级别下,如果我是两个事务通过 user_id 先后对表中的 id=1 的行进行修改(两个事务都还未提交),第一行是肯定会被锁住的,那第二行还可以像我上面这个例子一样进行修改吗?如果不行,原因是什么?


  • 表锁


每次进行操作都会锁住整张表。加锁快,开销小,不会产生死锁,锁粒度大,发生锁冲突的概率很高,表锁的使用场景一般用在数据迁移这种使用场景上,还有给表添加索引也会对整张表进行加锁,阻塞其他想访问被锁住的表的操作,并发性低。 我们可以使用 lock table 表名称 1 read(write),表名称 2 read(write)来对表进行加锁。 下面举一个表锁的例子,加深一下大家的印象。


现在有这么一张表 CREATE TABLE bank_account (id int(10) NOT NULL AUTO_INCREMENT,user_id int(10) NOT NULL DEFAULT '0' COMMENT '用户 id',user_name varchar(24) NOT NULL DEFAULT '' COMMENT '用户名称',account int(10) NOT NULL DEFAULT 0 COMMENT '用户账户金额',PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='银行账户表'测试数据 insert into bank_account VALUES(null, 1, '张三', 2000);insert into bank_account VALUES(null, 1, '李四', 500);insert into bank_account VALUES(null, 1, '王五', 1000)


加入我们银行现在要迁移一下用户的金额数据表,现在要对这张表进行加锁,可以允许他们继续看看自己的余额,但是在我迁移完之前,不能让他们改变自己的余额,避免数据丢失。 那么我们现在这样做



可以看到,当我们对表加了读锁以后,用户可以正常访问表的数据,但是当他想修改金额的时候,被阻塞住了,直到我们释放表锁,他才能继续执行自己的修改操作。



读锁是允许了读操作,锁定的写操作,那么写锁则是读写操作都进行锁定了。这里大家有兴趣的可以试试,这里不做过多赘述。

4.死锁演示

双方互相持有对方的锁而不进行释放,最终导致死锁。 举个例子



最终,事务 1 成功执行,事务 2 被回滚了。请注意,这里的操作顺序是事务 1 先开启,修改第一行,然后事务 2 开启修改第二行,然后事务 1 再修改第二行,最后是事务 2 修改第一行。

5.间隙锁

什么叫间隙锁?我先给你看一个现象。


![image](https://upload-images.jianshu.io/upload_images/24613101-88ebba924a


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


1868b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


前提:客户端 A 跟客户端 B 的隔离级别都是可重复读(只有这个隔离级别会发生间隙锁)


1.在客户端 A 开启了一个事务,然后执行了范围修改(事务还未提交)update bank_account set account = 10 where id > 5 and id < 8;2.在客户端 B 开启了一个事务,然后执行行修改 update bank_account set account = 200 where id = 10;3.注意看,客户端 B 修改 id 为 10 的这一行,被阻塞了。


原因是什么呢? 为什么我修改范围之外的行也会被锁住,难道是锁表了吗? 再看下面这张图,这明显不是锁表了。因为我更新 id 为 1 的行,又可以更新。



看完上面这个案例,我再来给你讲讲,什么叫做间隙锁。可以看到,客户端更新的 id 范围是 id 为 5-8 的这个区间。我们来仔细看一下 bank_account 这张表,你可以发现 id 并不是连续的,而是中间出现了几个间断的区间。如 [3,6],[7,10]。这两个间断的区间中的 id 是没有的,这些区间就叫做间隙。如上的例子 update bank_account set account = 10 where id > 5 and id < 8;这个范围刚好落在了[3,6],[7,10]两个间隙之间。那么,这个间隙区间,我们就可以认为已经被锁上了,而这把锁就叫间隙锁。他锁住了这个区间除开 3 跟 7 的所有范围。也就是说这两个区间,id 为 3 跟 id 为 7 的行我们还是可以访问,但是其他范围的行已经是都被上锁了。

三、对于几种并发出现的事务问题进行分析

并发事务如果隔离级别处理不当,可能会出现以下几种问题。其中,脏读的危害性最大,不可重复读跟幻读需要根据实际的业务场景来看。


  • 脏读


第二个事务读的到第一个事务未提交的数据,然后第一个事务发生异常进行了回滚,第二个事务仍以第一个事务回滚前的数据进行了接下来的操作。 举个例子: 1.张三账户有 2000 元,李四账户有 1500 元,张三给李四转了 500 元,这个时候银行系统因为网络问题,这个转钱的过程还没结束(也就是当前两个事务都还没有进行提交),


2.这个时候,李四去淘宝上买了一个 1800 元的手机,银行在处理买手机的操作时,读到李四的账户余额是这样的 李四原本的账户余额 1500 + 张三转的 500 = 2000


3.但是在这个时候,转账操作发生了异常,进行了回滚,张三账户重新变成了 2000 元,李四的账户重新变成 1500 元,但是李四却成功下单了 1800 元的手机。这不就乱套了吗? 所以,我们需要去避免脏读的情况发生


  • 不可重复读


一个事务在执行过程中相同的条件重复读取某一条记录,拿到的结果是不一致的,这就叫不可重复读 举个例子: 1.比如张三现在余额有 1000 元,他看了一下自己余额,发现自己可以买一个 500 元的风扇,于是下单买了一个风扇 2.这个时候,李四突然想起来欠了张三 500 元,于是他转了 500 元给张三 3.张三在买了风扇以后想看自己还剩多少钱 4.结果发现自己还有 1000 元


张三买风扇的过程你可以理解为开启了事务 A,事务 A 中一系列的主动操作(看余额-买风扇-看余额),你都可以理解为在同一个事务当中执行了两次读取数据操作. 只不过在第二次读取余额前,有另外一个事务给张三的余额转了 500 并且成功提交了。所以张三在第二次查余额的时候会看到 1000 元。


  • 幻读


这里同学们可能经常会把不可重复读跟幻读的概念搞混淆,幻读更看重的是新增。而不可重复度看重的是修改。那么什么叫幻读呢?也就是一个事务按相同条件重复读取记录,读取内容不一致,可以读到别人新增的数据。

四、数据库隔离级别对并发事务的影响-实战

对于并发事务的控制可以通过多种手段进行,比如加锁,比如事务隔离级别,比如 MVCC 机制。 这里我们针对数据库的四种隔离级别做一个简单的实战,来看一下数据库的四种事务隔离级别对并发事务的影响。 MySQL 事务隔离级别


通过 show VARIABLES like ‘tx_isolation’;查看 MySQL 当前的事务隔离级别

1.读未提交(Read uncommitted)

如果设置了未提交,在同一个事务中,可以读取到不同事务还未提交的数据。就有可能会发生脏读,幻读,不可重复读的问题。


设置数据库读未提交 set tx_isolation = ‘read-uncommitted’;


2.读已提交(Read committed)

在同一个事务中,当前事务可以读到其他事务修改并且已经提交的数据。


设置数据库读已提交 set tx_isolation = ‘read-committed’;


3.可重复读(Repeatable read,MYSQL 默认事务隔离级别)

在同一个事务中,当前事务不可以读到其他事务修改并且已经提交的数据。但是可以修改其他事务新增的数据,并且修改之后,可以读取到其他事务新增并且已经提交的数据。


设置数据库读已提交 set tx_isolation = ‘repeatable-read’;


当前未提交事务无法读取其他已经修改并提交的数据



当前未提交事务无法读取其他已经修改并提交的数据,但是可以修改其他事务新增的数据,并且修改之后,可以读取到其他事务新增并且已经提交的数据。听起来有点绕,看个例子,可能会让你更加好理解一些。



你可以把这种现象理解为幻读。

4.可串行化(Serializable)

串行化,第一个事务读取的数据或者操作的数据都会被数据库上锁,这是最好的数据库隔离级别,不会产生并发问题,但是发生并发事务时,允许的并发程度几乎为 0.工作中不建议使用这样的隔离级别。

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
小白必看!结合实际实例,理解事务,多线程面试题java