您的位置:首页 > 数据库 > MySQL

MySql 乐观锁 与 悲观锁 的概念与使用

2020-04-23 17:45 507 查看

MySql 的乐观锁 与 悲观锁

先上图:

乐观锁

​ 乐观锁,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。

​ 乐观锁适用于 读多写少 的应用场景,可以提高吞吐量。

​ 乐观锁:假设数据不会发生变化,只在提交操作时检查是否违反数据完整性。

乐观锁的两种实现方式:

  1. 使用数据版本(version)记录机制实现。

    即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “

    version
    ” 字段来实现。当读取数据时,将
    version
    字段的值一同读出,数据每更新一次,对此
    version
    值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的
    version
    值进行比对,如果数据库表当前版本号与第一次取出来的
    version
    值相等,则予以更新,否则认为是过期数据。

  2. 使用时间戳。

悲观锁

​ 悲观锁,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。

​ 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

Java synchronized
就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block。

实例:

​ 电商系统中的下单流程,商品的库存量是固定的,如何保证商品数量不超卖? 其实需要保证数据一致性:某个人点击秒杀后系统中查出来的库存量和实际扣减库存时库存量的一致性就可以。

​ MySql 数据库中商品库存表结构定义如下:

CREATE TABLE `tb_product_stock` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`product_id` bigint(32) NOT NULL COMMENT '商品ID',
`number` INT(8) NOT NULL DEFAULT 0 COMMENT '库存数量',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`modify_time` DATETIME NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`), // 主键
UNIQUE KEY `index_pid` (`product_id`) // 索引
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品库存表';

对应的实体类:

public class ProductStock {
private Long productId; //商品id
private Integer number; //库存量

public Long getProductId() {
return productId;
}

public void setProductId(Long productId) {
this.productId = productId;
}

public Integer getNumber() {
return number;
}

public void setNumber(Integer number) {
this.number = number;
}
}

如果不考虑并发的情况下,更新库存代码如下:

/**
* 更新库存(不考虑并发)
* @param productId
* @return
*/
public boolean updateStockRaw(Long productId){
ProductStock product = query("SELECT * FROM tb_product_stock WHERE product_id=#{productId}", productId);
if (product.getNumber() > 0) {
int updateCnt = update("UPDATE tb_product_stock SET number=number-1 WHERE product_id=#{productId}", productId);
if(updateCnt > 0){    //更新库存成功
return true;
}
}
return false;
}

但是在多线程并发情况下,会存在超卖的可能。

加上悲观锁的代码如下:

/**
* 更新库存(使用悲观锁)
* @param productId
* @return
*/
public boolean updateStock(Long productId){
//先锁定商品库存记录  “FOR UPDATE” 拿数据时上锁
ProductStock product = query("SELECT * FROM tb_product_stock WHERE product_id=#{productId} FOR UPDATE", productId);
if (product.getNumber() > 0) {
int updateCnt = update("UPDATE tb_product_stock SET number=number-1 WHERE product_id=#{productId}", productId);
if(updateCnt > 0){    //更新库存成功
return true;
}
}
return false;
}

加上乐观锁的代码如下:

/**
* 下单减库存
* @param productId
* @return
*/
public boolean updateStock(Long productId){
int updateCnt = 0;
while (updateCnt == 0) {
ProductStock product = query("SELECT * FROM tb_product_stock WHERE product_id=#{productId}", productId);
if (product.getNumber() > 0) {
updateCnt = update("UPDATE tb_product_stock SET number=number-1 WHERE product_id=#{productId} AND number=#{number}", productId, product.getNumber());
if(updateCnt > 0){    //更新库存成功
return true;
}
} else {    //卖完啦
return false;
}
}
return false;
}

使用乐观锁更新库存的时候不加锁,当提交更新时需要判断数据是否已经被修改(AND number=#{number}),只有在 number等于上一次查询到的number时 才提交更新。

乐观锁与悲观锁的区别

​ 乐观锁的思路一般是表中增加版本字段,更新时where语句中增加版本的判断,算是一种

CAS
(Compare And Swep)操作,商品库存场景中number起到了版本控制(相当于version)的作用(
AND number=#{number}
)。

​ 悲观锁之所以是悲观,在于他认为本次操作会发生并发冲突,所以一开始就对商品加上锁(

SELECT ... FOR UPDATE
),然后就可以安心的做判断和更新,因为这时候不会有别人更新这条商品库存。

ber起到了版本控制(相当于version)的作用(

AND number=#{number}
)。

​ 悲观锁之所以是悲观,在于他认为本次操作会发生并发冲突,所以一开始就对商品加上锁(

SELECT ... FOR UPDATE
),然后就可以安心的做判断和更新,因为这时候不会有别人更新这条商品库存。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: