任务描述
本关任务:使用共享锁更新表中数据。
相关知识
在事务的隔离级别内容中,能够了解到两个不同的事务在并发的时候可能会发生数据的影响。细心的话可以发现事务隔离级别章节中,脏读、不可重复读、幻读三个问题都是由事务 A
对数据进行修改、增加,事务 B
总是在做读操作。如果两事务都在对数据进行修改则会导致另外的问题:丢失更新。
这就是本实训所要学习的主题,同时引出并发事务对数据修改的解决方案。
为了完成本关任务,你需要掌握:1.丢失更新的定义及产生原因;2.如何解决丢失更新问题。
丢失更新的定义及产生原因
丢失更新就是两个不同的事务在某一时刻对同一数据进行读取后,先后进行修改,导致第一次操作数据丢失:
该问题和之前事务并发出现的几个问题需要区分开, 因为解决方案不是一类!此类问题,只能用最高隔离级别 Serializable
或者手动使用锁来解决,本关我们就一起学习如何使用锁来解决。
如何解决丢失更新问题
由第一关介绍存储引擎后,我们知道 InnoDB
存储引擎才支持事务,因此我们使用 行锁 来解决,我们还介绍了行锁包括悲观锁和乐观锁,悲观锁包含了读锁和写锁,下面我们就使用悲观锁的两种方式来解决。
使用共享锁
共享锁(S
),又称为读锁,获得共享锁之后,针对同一份数据,多个读操作可以同时进行,互不影响,但无法修改和删除数据。
例如:我在客户端 A
给数据 C
添加了共享锁,此时我在客户端 B
只能添加共享锁进行查看,没有修改的权利,如果我想要在客户端 B
进行修改,我只能在 A
处commit
才能进行修改。
在查询语句后面增加LOCK IN SHARE MODE
,MySQL
会对查询结果中的每行都加共享锁:
select ... lock in share mode;
使用排他锁
排他锁(X
),又称为写锁、独占锁。获得排他锁后,在当前写操作没有完成前,它会阻断其他写锁和读锁。
排它锁就是我在客户端 A
给数据 C
添加了排它锁,那么我在客户端 B
只能在客户端 A
commit
之后,才能select
数据。
换句话说,只要我在客户端 B
用锁进行了查询,那我我都需要等待 A
commit
之后,如果此时我客户端 B
不加锁,我是可以查询到的。
添加排它锁的方式:
select ... for update;
编程要求
在右侧命令行中编写代码,开启两个命令行窗口连接数据库,体验共享锁的使用,(当一个事务 A
连接在做更新操作的时候,另一个事务 B
连接不能进行更新操作,事务 A
提交后,事务 B
则可进行更新操作并提交,此时更新操作不会被丢失),具体操作如下:
-
点击
+
添加一个命令行窗口,在两个命令行中连接数据库(用户名为root
,密码为123123
); -
假设源命令行窗口为
A
连接,新增加的窗口为B
连接,,请在
A
中连接数据库后,执行平台/data/workspace/myshixun/src/step3/table.sql
的sql
文件,将会为你创建好mydb
数据库和account
表,执行命令为:source /data/workspace/myshixun/src/step3/table.sql;
-
在
A
、B
连接中均开启事务,使用共享锁查询account
表中原有余额,并在A
连接中将id
为1
的余额改为0
,id
为2
的余额改为200
,在B
连接中将id
为2
的余额改为0
。 -
此时
B
连接中在等待A
连接的更新操作完成(提交事务),先后将事务A
、B
提交即可,平台将输出你更新后的用户余额。
测试说明
平台会对你编写的代码进行测试:
预期输出:
+----+------+-------+
| id | name | money |
+----+------+-------+
| 1 | A | 0 |
| 2 | B | 0 |
+----+------+-
①:首先打开两个命令窗口, ![在这里插入图片描述](https://img-blog.csdnimg.cn/89e06952da0744cc9f6f0c5df82b2119.png) ②:在第一个窗口中执行如下命令: mysql -uroot -p123123 source /data/workspace/myshixun/src/step3/table.sql; begin; ③:在第二个窗口中执行如下命令: mysql -uroot -p123123 begin; ④:切到第一个窗口: select * from account lock in share mode; ⑤:切回第二个窗口: use mydb; select * from account lock in share mode; ⑥:再切到第一个窗口: update account set money=0 where id='1';//此时窗口在等待 ⑦:回到第二个窗口: update account set money=0 where id='2';//命令执行失败 ⑧:回到第一个窗口,上一个命令被执行,接着更新表中信息: update account set money=200 where id='2'; commit; ⑨:切到第二个窗口: update account set money=0 where id='2';//由于窗口一已经commit,因此再次执行命令时成功 commit; ⑩:点击评测
------+