在介绍for update之前我们先粗浅的了解一下mysql的锁机制。
按锁定粒度分为:
-
行锁:锁定一行数据
-
表锁:锁定整张表
按操作类型分为:
-
共享锁:也就是常说的读锁,各个事物共享读操作,阻塞写操作。
-
排他锁:写锁,当一个事务开启了排他锁,其他事务针对被上写锁的表或行数据都不能读或写
悲观锁:默认本次操作会对数据进行修改直接锁住数据,很悲观(被害恐惧症)。有点类似于java的synchronized。
乐观锁:默认操作不会对数据进行篡改,但防人之心不可无,乐观锁虽然不会主动锁住数据,它会采用一种 ‘版本号’ 的机制来防止资源争夺现象的发生。
(补充,悲观锁和乐观锁是一种思想,至于怎么实现要靠我们自己啦。。。)
针对于上述锁机制不太了解的同学可以参考大神的文章:
https://www.cnblogs.com/leedaily/p/8378779.html
我们先来设想一个环境。现有一张商品表 goods ,拥有三个字段
- 主键id
- 商品名称 good_name
- 商品数量 good_count
假设现在有两个事务A和B要对同一条数据进行修改。
在我们以前应该是这么做的:
1.先查出商品的数量
select good_count from goods where id =1 ;
然后判断商品数量是否大于0
2.如果大于0,修改数量
update good_count = good_count -1 where id = 1;
3.向订单表插一条数据
这么做看似没问题但是在第一步的时候有可能两个事务查到的数量是一样的,那么执行到第三步的时候就会向订单表插入两条数据,一个商品两条订单 这明显不符合业务了。
当我们正处于一个高并发环境下,商品数量少 但并发访问较大,很容易造成多个线程之间资源的争夺,造成超卖的现象。所以这个时候我们必然要给数据上锁 ,for update 就可以派上用场了。for update 可以给数据上共享锁。
首先在A中开启事务:
这里要补充一点:在innodb中,如果where后面的条件包括的是主键或索引字段 for update会锁定对应的数据行。 但如果是普通字段的话会锁住整张表 例如:where good_name =“xxx” for update .因为good_name没有创建索引,想解决这个问题需要在good_name字段加索引。
这时B事务执行同样的操作,他也要查询这个商品并上锁,但是A在它之前已经上锁了,所以B会阻塞 直到A释放锁为止。
A提交事务,B可以继续执行了。这样就可以保证数据的一致性了。在我看来,这也是mysql对于悲观锁的一种实现。