最早接触锁是在大学时在实验室做的第一个正儿八经的项目,一个大哥(我们都喊他磊神)带两个小弟,虽然是个小型业务系统,但是系统涉及到一些金额交易,保证数据的安全性还是重中之重,那会的我刚写明白crud,对于处理这些问题真是没吃过猪肉也没见过猪跑,所以磊神就自己操刀在会发生安全性问题的地方加上了锁,当时我以为磊神不给我们分享这个操作是因为他怕教会徒弟饿死师傅,现在看来怕是他当时也没整明白,就像加索引一样,网上说干啥能解决我就干啥...
两阶段锁协议
这里的乐观锁就是典型的行锁的解决方案,对应着还有悲观锁,每种都有不同的落地实现方式,有的是代码层面有的是数据库层面。
数据库锁就是为了用来处理并发问题,作为多用户共享的资源,当有并发请求的时候,数据库需要合理控制资源的访问规则,保证数据的安全可靠性。而锁就是用来实现这些访问规则的重要手段。根据加锁的范围,MySQL里面的锁大致可以分成全局锁、表级锁和行锁三类,这次只说行锁,行锁顾名思义,每次只有一个事务可以对这一行进行操作,其他事务只能等待。
在这个例子中事务A在提交事务之前持有id=1和id=2的锁,所以事务B会被一直阻塞到事务A提交后,但要注意一手,并不是开始事务之后就锁住了id=1和id=2而是需要的时候才锁,也就是执行到了那一行再锁,而且不是执行完了就释放锁而是事务整体提交后释放,这就是两阶段锁协议。
那么以后设计接口的时候如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。举个例子,假设你负责实现一个电影票在线交易业务,顾客A要在影院B购买电影票。我们简化一点,这个业务需要涉及到以下操作:
从顾客A账户余额中扣除电影票价;
给影院B的账户余额增加这张电影票价;
记录一条交易日志。
显然两条insert一条update为了保证原子性要放到一个事务中,如果这时顾客C也想买票发生冲突的地方就在2操作,所以逻辑顺序可以改为132,尽可能的降低影院B被锁时间。
假如影院搞活动可以低价预售一年内所有的电影票,而且这个活动只做一天。于是在活动时间开始的时候,你的MySQL就挂了。你登上服务器一看,CPU打满,但整个数据库每秒就执行不到100个事务。这是为啥呢。
多半是死锁
假设事务A执行完第一句还没来得及第二句时,事务B先获取了id=2的锁,而事务B想获得id=1的锁却被事务A拿走了,这样循环依赖就变成了死锁。而数据库处理死锁有两种策略
1、直接进入等待,直到超时。超时时间可通过innodb_lock_wait_timeout来设置。
2、发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启。
正常情况下我们还是要采用第二种策略,即:主动死锁检测,而且innodb_deadlock_detect的默认值本身就是on。
主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但东西都没有十全十美的。每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是O(n)的操作。
假设有1000个并发线程要同时更新同一行,那么死锁检测操作就是100万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的CPU资源。因此CPU利用率很高,但是每秒却执行不了几个事务就会出现上述场景的问题。那么如何解决呢,可以通过程序去控制并发量,也可以采用让更新同一行的请求入队列阻塞排队。
知道的越多不知道的就越多,这周末加两天班跟没放假似的,下周见~