对于锁的一些理解和解释

6 篇文章 0 订阅
4 篇文章 0 订阅

首先是解释死锁以及我们的死锁出现条件
两个语句是在事务内起作用的,所涉及的概念是行锁。它们能够保证当前session事务所锁定的行不会被其他session所修改(这里的修改指更新或者删除)。两个语句不同的是,一个是加了共享锁而另外一个是加了排它锁。可以这么理解,共享锁允许其他事务加共享锁读取,但是,不允许其他事务去做修改,或者加排它锁。而排它锁显得更加严格,不允许其他事务加共享锁或者排它锁,更加不允许其他事务修改加锁的行。

其实,说白了就是在事务里面出现的问题
事务四大特性:
事务是必须满足4个条件(ACID)
1、 原子性(Autmic):事务在执行性,要做到“要么不做,要么全做!”,就是说不允许事务部分得执行。即使因为故障而使事务不能完成,在rollback时也要消除对数据库得影响!
2、 一致性(Consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。在事务开始之前和结束之后,数据库的完整性约束没有被破坏
3、 隔离性(Isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰,这些通过锁来实现。
4、 持久性(Durability):指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障(比如说宕机等)不应该对其有任何影响。
事务的ACID特性可以确保银行不会弄丢你的钱,而在应用逻辑中,要实现这点非常难,甚至可以说是不可能完成的任务。

MySQL事务处理的方法:

1、 用BEGIN,ROLLBACK,COMMIT来实现
START TRANSACTION | BEGIN [WORK] 开启事务
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE] 提交当前事务,执行永久操作。
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE] 回滚当前事务到开始点,取消上一次开始点后的所有操作。

我们来想一下,在我想对一个参数进行修改的时候,如果有别的用户也想修改会怎么样,没错,按照正常的道理来说,就是先后顺序,但是事务不一样,事务需要的是一个过程,也就是开始(begin)然后执行过程,最后是提交或者回滚,那这个时候就会出问题了,我们这边演示一下,并且开始讲解锁的概念
事务执行机a:

mysql> select * from data;
+--------+
| name   |
+--------+
| dd     |
| 阿飞   |
+--------+
2 rows in set (0.00 sec)
mysql> begin;                                      #事务开始
Query OK, 0 rows affected (0.00 sec)

mysql> update data set name='aa' where name='bb';  #对bb这一个数据进行修改,修改成aa
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

好了,我们这个时候不进行提交或者回滚,我们去另一台机子上执行另一个修改

mysql> update data set name='cc' where name='dd';
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

等了很久你会发现,此时报了个,错误1205 (HY000):锁定等待超时超过;试着重新启动事务
欸,这里报了个锁的问题,这是个什么鬼问题,锁是什么
我们现在去事务执行机a上结束事务commit

mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from data;
+--------+
| name   |
+--------+
| aa     |
| 阿飞   |
+--------+
2 rows in set (0.00 sec)
好了,我们现在需要去理解一下锁的问题,好在后面的死锁问题上可以解决更加顺利

乐观锁
乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。
通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。
举例:

下单操作包括3步骤:
1.查询出商品信息
select (status,status,version) from t_goods where id=#{id}
2.根据商品信息生成订单
3.修改商品status为2
update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};

除了自己手动实现乐观锁之外,现在网上许多框架已经封装好了乐观锁的实现,如hibernate,需要时,可能自行搜索"hiberate 乐观锁"试试看。

悲观锁
与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。
说到这里,由悲观锁涉及到的另外两个锁概念就出来了,它们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。

共享锁
共享锁指的就是对于多个不同的事务,对同一个资源共享同一个锁。相当于对于同一把门,它拥有多个钥匙一样。就像这样,你家有一个大门,大门的钥匙有好几把,你有一把,你女朋友有一把,你们都可能通过这把钥匙进入你们家,一下理解了哈,没错,这个就是所谓的共享锁。
刚刚说了,对于悲观锁,一般数据库已经实现了,共享锁也属于悲观锁的一种,那么共享锁在mysql中是通过什么命令来调用呢。通过查询资料,了解到通过在执行语句后面加上 lock in share mode就代表对某些资源加上共享锁了。

排它锁
排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁。
与共享锁类型,在需要执行的语句后面加上 for update就可以了
排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制
行锁
行锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。
比如之前演示的共享锁语句
SELECT * from city where id = “1” lock in share mode;
由于对于city表中,id字段为主键,就也相当于索引。执行加锁时,会将id这个索引为1的记录加上锁,那么这个锁就是行锁。

表锁
表锁,和行锁相对应,给这个表加上锁。

我们有个初步了解之后可以进行验证了,乐观锁一般是自己创建的,因此不在我们研究范围,我们研究可以在mysql里面快速创建的悲观锁

我们现在先不直接整这些,我们来看看,如果我在两台机子上同时执行两个事务,两个事务同时修改一个数据,此时就会报我们的死锁问题了

事务执行机a:

mysql> select * from data;
+--------+
| name   |
+--------+
| aa     |
| 阿飞   |
+--------+
2 rows in set (0.00 sec)
mysql> begin;                                     #开始执行
Query OK, 0 rows affected (0.00 sec)
mysql> update data set name='cc' where name='aa'; #执行将aa改成cc
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

好了,这个时候我们去第二个会话上执行另一个事务
事务机b

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update data set name='bb' where name='dd';
#如果你此时事务a机快速执行commint就会返回一个,你可以看到changed是为0的,没有进行任何修改
Query OK, 0 rows affected (13.27 sec)
Rows matched: 0  Changed: 0  Warnings: 0
#如果你等了很久,事务机a一直没有解放锁,那么就会报一个等待锁超时
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
这个修改在这里就会卡着了,所以说,此时a事务机给这一行数据上了锁,b事务机的自然是无法进行上锁的,这样的话,我们也能可以引出一个事情了,update事务会给这个数据上一个行锁,行锁类型为排他锁
我们将事务机a进行commit
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from data;
+--------+
| name   |
+--------+
| cc     |
| 阿飞   |
+--------+
2 rows in set (0.00 sec)
好了,内容修改完成,此时我们去事务机b上执行commit并且查看一下
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from data;
+--------+
| name   |
+--------+
| cc     |
| 阿飞   |
+--------+
2 rows in set (0.00 sec)
没有任何的修改

共享锁,顾名思义,你如果需要的话,每个事务都可以为其上锁。
但是,有没有想过,如果是同时修改,那到底是修改谁的呢,毕竟你上了共享锁,别人也可以上锁,那也就是说明,有些缺德的家伙可能会想,如果别人加了共享锁,我去上个排他锁,不是就可以抢锁了吗?
我们验证一下,事务A

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from data where name='cc' lock in share mode;
Empty set (0.00 sec)
事务B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from data where name='cc' for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

可以看到,超时了,也就说明,是没有办法抢锁的哦

排他锁,一般来说,我们执行的改动语句里面都会含有排他锁,让别人不能够同时修改数据
效果和上面的是一样的

我们重点讲解一下死锁
所谓死锁,也就是你挟持一个人质,我挟持一个人质,你叫我放人,我叫你放人,两个都在等,但并没有驱动让两者放人,因此陷入死局,这个也是死锁的本质
事务A查询数据A并给数据A上了锁,事务B查询数据B并给数据B上了锁,此时事务A想要修改数据B,事务B想要修改数据A,从而导致死锁,两方都在等放人

其实我们很多情况下,不需要自己去解决,因为MySQL已经帮我们检测了,一旦检测到死锁,就会停止掉一个最少行级排他锁的事务进行回滚,然后,另一个事务就会成功修改,我们需要的是后续的调试,防止下次再次出现死锁,毕竟,问题摆在那里,终归是要解决的

下面的就是查看死锁事务的
show engine innodb status

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值