【三大锁】悲观锁——mysql悲观锁
一 三大常用锁
- 悲观锁
- 你准备去银行取10w了,跟银行提前打个招呼,有个10w现金谁都别动。(一般只限制写-别人不能取那10w但是能查到银行总体余额,某些场景会限制读)
- 乐观锁
- 你准备取10w了,不打招呼,撸起袖子(开始事物,准备好回滚),直接去银行,到了之后,银行告诉你10w在那里,你正好取走(事物提交)。 也有可能去了后,银行说10w没了,那你不取了(事物回滚)
- 这里面涉及到一个细节,你拿的10w可能是 路人甲 早上取走了最后10w,然后在你到达之前 路人乙 存了10w。 如果你的业务场景是必须要银行的新钱,而不是别人刚存的,那你可以问下银行这个钱是否有人动过(标记操作流水版本号,cas算法的特点)
- 分布式锁
- 饭店门口很多顾客(多个访问线程),但是餐桌资源只有空闲的时候才会让人进去。有客户取消或者用餐完毕,某个4人桌就空闲出来了,你就可以去抢占这个4人桌的号(分布式锁),先来先得!(和排号系统还是有点不一样的)
二 实现方式
- 分布式锁
- 基本都是拿悲观锁(如 redis: setnx+看门狗+unique_value)
- 乐观锁
- redis的watch
- mysql的step1: select step2: update .set. where id = pre_id and version = pre_version 然后判断result是否>0
- cas算法基本原理类似,需考虑version
- 悲观锁
- reids的setnx + 看门狗
- 不限制消息发送频率只限制重复发送的场景,可以带上msgid作为setnx的value
- 怕del错lock,可以把uniq_id 作为setnx的value,然后del之前,get判断uniq_id是否一致! get和del可以通过lua制作成原子命令!
- mysql的select for update (本文重点)
三 mysql悲观锁
用户x下单,系统查询是否有存货,生成订单balabala ( 我们称为process ),存货-1
process的过程中,可能用户y 就下单了,或者比你下单早的用户z 刚好结算到存货-1了,那用户x 直接把库存降到-1了。 虽然可能有数据库unsigned等保护,那如果库存设置的是底货必须是10以上呢?
所以我们要用到select .. for update
这玩意就是把select 直接锁住,不让别人增删改(查允许)
如果命中了索引,含主键索引、唯一索引,那么是行锁;否则是表锁;
cmd1 不commit
cmd2 会阻塞
然后cmd1 commit;则cmd2 success
如果cmd2要update等操作,会命中cmd1的查询结果行(只要包含cmd1的结果,你想想,别人锁住了,你却要动他,虽然你要动1w行,只有其中一行是cmd1的结果,但是就是卡住了,没办法!), 则会阻塞,时间久了会down;未命中cmd1的查询结果行,则没有任何影响。
cmd1如果resulet为空,那么根据上条结论,也能知道,相当于没锁。
因为name是非索引,这时候直接锁表了,虽然和cmd1的result无关,还是卡住了!