Mysql行锁是在引擎中实现的,并不是所有的存储引擎都支持行锁,比如myisam就不支持行锁,而innodb支持行锁,myisam在并发度高的系统中就会影响系统的性能,因为他仅仅支持表锁,这也就是他被innodb替换的原因之一
行锁顾名思义,就是当一个事物A更新一行的数据的,事物B也要更新同一行,因此事物B必须等待事物A执行完成之后才会执行,
有些情况行锁会引起不必要的问题,首先我们了解一下二阶段锁说起
正如下面例子,事物B是什么时候才回去执行呢
其实这个问题取决于事物A是在什么时候执行完成,当事物A执行完成之后,事物B不一定立马会执行。直到事物A是在提交事物之后,才会释放行锁,知道了这个原理,发现并不是不需要的时候就去释放行锁,而是等待事物提交之后才会真正的释放锁,这就是二阶段锁
那么这能给我们业务系统中起到什么作用呢,在事物中含有多个行锁的时候,尽量要把引起所冲突,最可能引起并发的锁向后拖,举个例子 在某电影院中,一个人A需要购买电影票,主要涉及下面几种业务操作
- 扣除我们账户的金额
- 在电影院中增加电影票的金额
- 记录一条都买记录
与此同时,另外一个人B也需要购买电影票,这个时候,就必须等待A操作完毕之后,B的业务逻辑才能进行,这种情况要是在高并发中,可能就会有很高的延迟性,根据二阶段锁定义,我们是必须等待A这个人购买完之后,才能进行另一个人B的操作,但是我们认真的分析了一下,其中有锁冲突的就仅仅是对电影院中账户增加金额,才会导致锁冲突,那么就可以移动操作的业务逻辑顺序,比如我们执行的顺序是1,3,2.这样我们就尽可能的把锁冲突的时间较少到了做少,这样就会间接的提高了并发度。 但是上面的优化并没有真正的解决问题,如果当电影院进行活动,用户量非常的高,我们发现cpu达到了100%,但是并没有多少事物被执行。如何解决的呢,到这里我们必须了解几个概念
死锁和死锁检测
并发系统中多个不同线程循环依赖资源,在多个线程就会等待其他线程释放资源,互相等待,这就是死锁,举个例子
看到上面的例子,我们发现事物A执行id=2的时候,在等待事物B释放id=2的锁,而事物B在执行id=1的时候,在等到事物A释放id=1的锁。互相循环等待,如何解决这种问题呢,有两种策略
- 设置超时时间,事物超过了时间就会自动释放,可以用nnodb_lock_wait_timeout设置
- 死锁检测,检测到有死锁,释放其中一条,让其他事物先进行,可以用innodb_deadlock_detect设置为on,表示开启这个逻辑
如果我们设置超时时间,默认是50s,在并发系统中,我们必须等待超过50s才会释放锁,这对于正常的业务是不能接受的,那么我们如果把超时间设置成更短呢,比如1s,这种也是不行的,我们正常的业务系统中,有可能发生正常的锁等待,这就可能导致误杀,也是不能接受的。
如果我们使用死锁检测呢,一个事物操作数据的时候,就会检测是否有依赖的资源,导致死锁,那么他能快速的进行处理,但是也是有额外的开销的
在一个高并发的系统中,有1000个线程并发执行同一行数据,就会导致100万级别的计算检测,这样也会导致系统高延迟,cpu急剧上升,也执行不了多少事物。
那么我们如何解决这种热点行导致的问题的,当然也是有的
比如我们可以肯定要操作的行数据是不会发生锁冲突的,我们就可以关闭死锁检测,这种我们的系统会大量超时,对业务是有损的,业务对于死锁看做并不是一种很验证的错误,发生的时候,使用死锁检测可以进行回滚,对业务是无损,因此如何选择你自己看着办。
另一种思路就是提高并发控制,对一行数据仅仅有10个线程操作,那么死锁检测就不会有很大的成本,最直接的想法就是设置客户端的并发线程最多是5个,但还是如果有的系统由很多个客户端,那也有不小的并发线程,因此我们只能在服务端进行控制
因此我们可以利用中间件进行控制,当然如果你们的团队有能力修改存储引擎,让线程排队处理,也是可以的,但是正常情况很少有这样的人才,反正我们的团队是没有的,只有问题出现了,互相推锅的人才,防不胜防,
我们也可以换种思路,比如把一行数据改成多个,让不能的线程处理不同行,比如,电影院的账户记录,可以拆成多行,只要正常添加金额就可以了,但是要注意可能退票的场景,导致金额为0的时候,这个时候需要特殊处理进行了
今天我们主要说了行锁,二阶段锁,以及死锁,死锁检测,如何处理热点行的处理,提供了几种方案,以及二阶段锁,事物中有多条行锁,尽量把有所冲突的行向后拖,但是这种也不能解决问题,才会引入死锁和死锁检测,主要在减少死锁上方向上,就是对并发资源的控制.