今天继续来讲面试,已经出了将近十个美团java一面真题系列文章了,今天来讲一讲mysql死锁发生的原因和解决,相信大多数小伙伴对mysql不陌生甚至经常在使用。但是可能对mysql锁这方面还不太熟悉,但是经常面试的时候却经常会被问到,我们根据面经来进行补短板,查漏补缺。下面开始今天的干货内容吧,走起
假如,A准备给B转钱,而B在同时也在给A转钱。
我的理解是,在数据库方面,就同时有两个事务,第一个,是A操作带来的,他锁定自己的行信息,修改余额,然后准备去锁定B的行信息;第二个,是B带来的,B先锁定自己的行信息,修改余额,然后准备去锁定A的行信息。
由于同时发生的,那么他们就死锁了。
问题是:
1,场景如第一行,是同时互相转钱,我这样的后台数据库操作逻辑对不对?
2,如果是对的,死锁是否会发生?如果是不对的,该如何设计后台实现?
3,如果死锁会发生,该如何避免,mysql的事务自动会处理(有人说会自动处理),那我能不能代码的设计上,避免这种死锁的情况?
1. 这种情况是有可能导致死锁的,A在等待B释放资源,B在等待A释放资源,相互等待资源,造成死锁。如果出现死锁会报ERROR,可在日志里查询到,已经出现死锁的情况,mysql会自动检测到了两个会话互相等待锁的情况,然后把最后一个会话去做回滚操作。
2. 针对这个问题避免死锁的方法:
① 设置锁优先级:提前设置优先级,如果运行A和B出现死锁,优先级低的回滚,优先级高的先执行,这样即可解决死锁问题。
② 以固定顺序访问:设定一个顺序,比如先A后B,或者先B后A,保证不管在什么时候都尊重这个顺序(通常是按ID大小的顺序),这样就会减少死锁发生的概率了。
③ 设置锁超时时间set lock_timeout:尝试获取锁的时候加一个锁超时时间,超过这个时间放弃对该锁请求。比如设置A的超时时间为10毫秒,B为100毫秒,A试了10毫秒以后获取不到资源,然后会自动断开,A断开了,这时B就可以获取资源了,避免了死锁。(但是这个方法不太好的地方在于,还需要对A再提交一次,而且timeout时间需要综合很多其他因素去设置)
④ 对所使用的数据全部加锁:每一个事务一次就将所有要使用到的数据全部加锁,否则就不允许执行,比如A在给B转钱的时候,会使用到A账户转账前,A相互转账后,B账户转账前,B账户转账后,所以就算是A给B转账,也要把A、B账户所有信息都一起加锁(这样B想给A转账也不行,因为被锁住了,不过这个还是傻,效率很低,可能又会带来其他死锁问题) 以上是能想到的解决你提出的这个场景的几种方法,关于避免死锁的方法太多了,还有其他建立索引, 设置事务隔离级别,编写应用程序让进程持有锁的时间尽可能短,等等。
下面来系统学习学习mysql事务及锁机制
1. 银行转账业务
A向B转账1000,A-1000,B+1000
2. 网上购买商品业务
数据库事务是构成单一逻辑工作单元的操作集合
注意点:
1. 数据库事务可以包含一个或多个数据库操作,但这些操作构成一个逻辑上的整体
2. 构成逻辑整体的这些数据库操作,要么全部执行成功,要么全部不执行
3. 构成事务的所有操作,要么全都对数据库产生影响,要么全都不产生影响,即不管事务是否执行成功,数据库总能保持一致性状态
4. 并发操作下,事务的控制尤为关键
引入事务时如何解决问题呢?
(1)原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
(2)一致性:事务的执行结果必须使数据库从一个一 致性状态到另一 个一致性状态。一致性状态是指:1.系统的状态满足数据的完整性约束(主码,参照完整性,check约束等) 2.系统的状态反应数据库本应描述的现实世界的真实状态,比如转账前后两个账户的金额总和应该保持不变。
(3)隔离性:并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。
(4)持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
在事务的四个特点中,一致性是事务的根本追求,而在某些情况下会又会对一致性造成破坏:
数据库系统通过并发控制技术和旦志恢复技术来避免这种情况的发生
Undo Log是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版本并发控制(简称: MVCC)
在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地万称为Undo Log),然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态
注意: undo log是逻辑日志,可以理解为:
和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是RedoLog已经持久化。系统可以根据Redo Log的内容,将所有数据恢复到最新的状态。
事务具有隔离性理论上来说事务之间的执行不应该相互产生影响,其对数据库的影响应该和它们串行执行时一样。
然而完全的隔离性会导致系统并发性能很低降低对资源的利用率,因而实际上对隔离
性的要求会有所放宽,这也会一定程度造成对数据库一致性 要求降低
SQL标准为事务定义了不同的隔离级别,从低到高依次是
不同的隔离级别可能导致不同的并发异常,如下图:
事务的隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ UNCOMMITTED) | |||
读已提交(READ COMMITTED) | |||
可重复读(REPEATABLE READ) | |||
串行化(SERIALIZABLE) |
在mysql中,锁可以分为两类:
锁的粒度:锁定对象的大小是锁的粒度:
可能出现的问题:
死锁:多个事务持有锁并互相循环等待其他事务的锁导致所有事务都无法继续执行
扩展:除了锁可以实现并发控制之外,还有其他策略:
共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据库,但是只能读不能修改;
事务A:
select * from student where id= 1 lock in share mode;
事务B:
select * from student where id = 1;(读取数据没问题)
事务B:
update student set name='hehe' whereid =1;
事务B: 无法修改会卡死,当事务A提交事务之后,会立刻修改成功
排它锁不能与其他锁并存,如一个事务获取了一个数据行的排它锁,其他事务就不能再获取改行的锁,只有当前获取了排它锁的事务可以对数据进行读取和修改。
delete、update、 insert默认是排他锁
事务A:
select * from student where id= 1 for update;
事务B:
select * from student where id= 1 for update;
select * from student where id= 1 lock in share mode;
注意:事务B操作的时候回卡死,提交事务立马成功。
意向共享锁: 表示事务准备给数据行加入共享锁,也就是说一个数据行在加共享锁之前必须先取得该表的IS锁
意向排他锁: 表示事务准备给数据行加入排它锁,也就是说一一个数据行加排它锁之前必须先取得该表的IX锁。
意向锁是InnoDB数据操作之前自动加的,不需要用户干预
针对自增列自增长的一个特殊的表级别锁
SHOW VARIABLES LIKE ' innodb_ _autoinc_ .lock_ mode';
-.-
默认值1代表连续,事务未提交则id永久丢失
事务的执行流程如下:
由于数据库存在立即修改和延迟修改,所以在事务执行过程中可能存在以下情况:
END