乐观锁 悲观锁 共享锁 排他锁

乐观锁

概念

乐观锁是逻辑概念上的锁,不是数据库自带的,需要我们自己去实现。
乐观锁是指操作数据库(更新操作)时,想法很乐观,认为这次操作不会导致冲突,在操作数据库时,并不进行任何其他特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突。

实现

在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。
也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等。
如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;
如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。

例,下单操作包括3步骤:

  1. 查询出商品信息

    1
    select (status,version) from t_goods where id=#{id};
  2. 根据商品信息生成订单

  3. 修改商品status为2

    1
    update t_goods set status=2, version=version+1 where id=#{id} and version=#{version};

悲观锁

概念

悲观锁就是在操作数据时,默认此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。
悲观锁的实现,依靠数据库提供的锁机制,使用的时候直接调用数据库的相关语句就可以了。

数据库上的操作可以归纳为两种:读和写。多个事务同时读取一个对象的时候,是不会有冲突的;同时读和写,或者同时写才会产生冲突。

MyIsam引擎会为查询和更新等操作自动添加表级锁,因此它的情况比较简单。
InnoDB引擎情况比较复杂,它通常会定义两种锁:共享锁排它锁

共享锁

概念

共享锁(Shared Lock,也叫S锁)表示对数据进行读操作,加锁后其他事务可以读,但不能写。多个事务可以同时为一个对象加共享锁。

实现
1
SELECT ... LOCK IN SHARE MODE

这是IS锁(意向共享锁),即在符合条件的rows上都加了共享锁,这样的话,其他人可以读取这些记录,也可以继续添加IS锁,但是无法修改这些记录直到你这个加锁的过程执行完成【完成的情况有:事务的提交,事务的回滚,否则直接锁等待超时】。

SELECT ... LOCK IN SHARE MODE的应用场景适合于两张表存在关系时的写操作,拿mysql官方文档的例子来说:一个表是child表,一个是parent表,假设child表的某一列child_id映射到parent表的c_child_id列,那么从业务角度讲,此时我直接insert一条child_id=100记录到child表是存在风险的,因为刚insert的时候可能在parent表里删除了这条c_child_id=100的记录,那么业务数据就存在不一致的风险。正确的方法是再插入时执行select * from parent where c_child_id=100 lock in share mode,锁定了parent表的这条记录,然后执行insert into child(child_id) values (100)就不会存在这种问题了。

排他锁

概念

排他锁(Exclusive Lock,也叫X锁)也叫写锁。加锁后,其他事务不能读取,也不能写。
如果一个事务对对象加了排他锁,其他事务就不能再给它加任何锁了。

实现

产生排他锁的sql:

1
SELECT ... FOR UPDATE

总结

  1. 要记住锁机制一定要在事务中才能生效,事务也就要基于MySQL InnoDB 引擎。
  2. 访问量不大,不会造成压力时使用悲观锁,面对高并发的情况下,我们应该使用乐观锁。
  3. 读取频繁时使用乐观锁,写入频繁时则使用悲观锁。还有一点:乐观锁不能解决脏读的问题。
  4. 共享锁适用于两张表存在业务关系时的一致性要求,排它锁适用于操作同一张表时的一致性要求。