场景描述

今天在 review 代码的时候,发现代码中的一个并发 BUG。

记录一下,便于以后查阅。

BUG

  • 问题

所有的查询之后,再更新同一条订单,在并发情况下是有问题的

  • 场景

同时 2 个 rpc 回调,A 撤销 order,B 更新 order。

进程 A、B 同时查询订单 order(状态正常)。

实际上,A 操作之后,B 实际上是不允许的。

但是二者初始的 order 状态却是相同的。

  • 解决方案
  1. 查询加锁。
select ... for update

锁的概念

在 MySQL 中的行级锁,表级锁,页级锁中介绍过,行级锁是 Mysql 中锁定粒度最细的一种锁,行级锁能大大减少数据库操作的冲突。

锁分为悲观锁,乐观锁。

共享锁(Share Lock)

共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。

如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。

用法

SELECT ... LOCK IN SHARE MODE;

在查询语句后面增加 LOCK IN SHARE MODE,Mysql 会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。 其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。

排他锁(eXclusive Lock)

排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。

用法

SELECT ... FOR UPDATE;

在查询语句后面增加 FOR UPDATE,Mysql会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。

意向锁

InnoDB还有两个表锁:

意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁

意向排他锁(IX):类似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。

意向锁是InnoDB自动加的,不需要用户干预。

对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);对于一般的Select语句,InnoDB不会加任何锁,事务可以通过以下语句给显示加共享锁或排他锁。

乐观锁

概念

相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个”version”字段来实现。

工作原理

读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。 此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

手动实现

-- 查询出商品信息

select (status,status,version) from t_goods where id=#{id}

-- 根据商品信息生成订单

-- 修改商品 status 为 2

update t_goods 

set status=2, version=version+1

where id=#{id} and version=#{version};

ps: 这里如果存在并发,就会导致更新无效的问题。

hibernate

Hibernate为乐观锁提供了3中实现:

  1. 基于version

  2. 基于timestamp

  3. 为遗留项目添加添加乐观锁

参考资料

  • 共享锁(S锁)和排它锁(X锁)

https://www.cnblogs.com/panxuejun/p/8874321.html

https://www.cnblogs.com/aipiaoborensheng/p/5564525.html

https://blog.csdn.net/luo809976897/article/details/54017309

  • 各种锁

https://blog.csdn.net/puhaiyang/article/details/72284702

http://www.hollischuang.com/archives/898

  • hibernate 乐观锁

https://www.cnblogs.com/otomedaybreak/archive/2012/01/27/2330008.html

  • mysql 锁详解

https://blog.csdn.net/soonfly/article/details/70238902