悲观锁

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

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

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

乐观锁

乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。

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

乐观锁一般来说有以下2种方式:

  1. 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
  2. 使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

Java JUC中的atomic包就是乐观锁的一种实现,AtomicInteger 通过CAS(Compare And Set)操作实现线程安全的自增。

两阶段锁协议,整个事务分为两个阶段,前一个阶段为加锁,后一个阶段为解锁。在加锁阶段,事务只能加锁,也可以操作数据,但不能解锁,直到事务释放第一个锁,就进入解锁阶段,此过程中事务只能解锁,也可以操作数据,不能再加锁。两阶段锁协议使得事务具有较高的并发度,因为解锁不必发生在事务结尾。它的不足是没有解决死锁的问题,因为它在加锁阶段没有顺序要求。如两个事务分别申请了A, B锁,接着又申请对方的锁,此时进入死锁状态。

MySQL隐式和显示锁定

MySQL InnoDB采用的是两阶段锁定协议(two-phase locking protocol)。在事务执行过程中,随时都可以执行锁定,锁只有在执行 COMMIT或者ROLLBACK的时候才会释放,并且所有的锁是在同一时刻被释放。前面描述的锁定都是隐式锁定,InnoDB会根据事务隔离级别在需要的时候自动加锁。

另外,InnoDB也支持通过特定的语句进行显示锁定,这些语句不属于SQL规范:

  • SELECT ... LOCK IN SHARE MODE
  • SELECT ... FOR UPDATE

实战

接下来,我们通过一个具体案例来进行分析:考虑电商系统中的下单流程,商品的库存量是固定的,如何保证商品数量不超卖? 其实需要保证数据一致性:某个人点击秒杀后系统中查出来的库存量和实际扣减库存时库存量的一致性就可以。

假设,MySQL数据库中商品库存表tb_product_stock 结构定义如下:

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='商品库存表';

对应的POJO类:

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){
//先锁定商品库存记录
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时 才提交更新。

** 注意** :UPDATE 语句的WHERE 条件字句上需要建索引

乐观锁与悲观锁的区别

乐观锁的思路一般是表中增加版本字段,更新时where语句中增加版本的判断,算是一种CAS(Compare And Swep)操作,商品库存场景中number起到了版本控制(相当于version)的作用( AND number=#{number})。

悲观锁之所以是悲观,在于他认为本次操作会发生并发冲突,所以一开始就对商品加上锁(SELECT ... FOR UPDATE),然后就可以安心的做判断和更新,因为这时候不会有别人更新这条商品库存。

小结

这里我们通过 MySQL 乐观锁与悲观锁 解决并发更新库存的问题,当然还有其它解决方案,例如使用 分布式锁。目前常见分布式锁实现有两种:基于Redis和基于Zookeeper,基于这两种 业界也有开源的解决方案,例如 Redisson Distributed locksApache Curator Shared Lock,这里就不细说,网上Google 一下就有很多资料。

链接:https://www.jianshu.com/p/f5ff017db62a

MySQL 乐观锁与悲观锁的更多相关文章

  1. mysql的锁--行锁,表锁,乐观锁,悲观锁

    一 引言--为什么mysql提供了锁 最近看到了mysql有行锁和表锁两个概念,越想越疑惑.为什么mysql要提供锁机制,而且这种机制不是一个摆设,还有很多人在用.在现代数据库里几乎有事务机制,aci ...

  2. 【数据库】mysql深入理解乐观锁与悲观锁

    转载:http://www.hollischuang.com/archives/934 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时 ...

  3. Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景

    一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |--排他锁(X锁,MyISAM 叫做写锁) |--悲观锁( ...

  4. Mysql乐观锁与悲观锁

    乐观锁和悲观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常重要的基础理念. Mysql的悲观锁 什么是悲观锁(Pessimistic Lock): 悲观锁的特点是先获取锁,再进行业务操作,即 ...

  5. [转]MySQL中乐观锁、悲观锁(共享锁、排他锁)简介

    InnoDB与MyISAM Mysql 在5.5之前默认使用 MyISAM 存储引擎,之后使用 InnoDB. MyISAM 操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高 ...

  6. mysql中的乐观锁和悲观锁

    mysql中的乐观锁和悲观锁的简介以及如何简单运用. 关于mysql中的乐观锁和悲观锁面试的时候被问到的概率还是比较大的. mysql的悲观锁: 其实理解起来非常简单,当数据被外界修改持保守态度,包括 ...

  7. MySQL学习(四)深入理解乐观锁与悲观锁

    转载自:http://www.hollischuang.com/archives/934 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据 ...

  8. 老司机带大家领略MySQL中的乐观锁和悲观锁

    原文地址:https://cloud.tencent.com/developer/news/227982 为什么需要锁 在并发环境下,如果多个客户端访问同一条数据,此时就会产生数据不一致的问题,如何解 ...

  9. 浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景

    浅谈Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景   Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景 一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁 ...

随机推荐

  1. ubuntu下ssh设置firefox用的反向代理

    mark一下: ssh -D 127.0.0.1:8080 -l root MyIp

  2. linux上定时备份mysql数据库

    定时备份数据库 /usr/sbin/backupmysql timestamp=`date +"%Y-%m-%d-%H-%M-%S"` mysqldump -uroot -p'12 ...

  3. lua的时间和日期函数

    lua的时间和日期函数 -- ::| 分类: Lua | 标签:lua 时间 函数 |举报|字号 订阅 下载LOFTER客户端 --获取当前的时间戳,单位是秒. time=os.time(); pri ...

  4. 如何使用NFS

    这两个网卡之间可以通信的,如图所示 也可以写成具体的ip地址,或者ip地址段例如192.168.1.2-200 注意看下面这幅图,路径一定要写完整 下面开始挂载共享的目录 挂载之后什么都没有出现说明挂 ...

  5. Unity3D避免代码被反编译

    1.Unity编译后最终会将代码编译在dll里面,无论是ios还是Android解开包以后都可以拿到dll,路径在Data/Managed/Assembly-CSharp.dll 2.IOS其实不用做 ...

  6. python zlib压缩存储到mysql列

    数据太大压缩存储,可以使用zlib中的压缩函数,代码如下: import ujson import MySQLdb import zlib import base64 kwargs = { 'host ...

  7. 小程序组件与api

    通过组合基础组件进行快速开发. 组件是视图层的基本组成单元. 所有组件都有的属性: 属性名 描述 注解 id 组件的唯一标示 保持整个页面唯一 class 组件的样式类 在对应的 WXSS 中定义的样 ...

  8. 【转】不用程序mysql也可以查询今天、昨天、7天、近30天、本月、上一月的数据

    今天   select * from 表名 where to_days(时间字段名) = to_days(now());   昨天 SELECT * FROM 表名 WHERE TO_DAYS( NO ...

  9. C语言编写的PHP框架--yaf入门编程

    首先--添加dll,修改php.ini--不同的版本,不同的需求 其次,根据教程http://www.laruence.com/manual/tutorial.firstpage.html#tutor ...

  10. 嵌入式开发之信号采集同步---VSYNC和HSYNC的作用以及它们两者之间的关系

    VSYNC和HSYNC的作用以及它们两者之间的关系 VSYNC和HSYNC的作用以及它们两者之间的关系 VSYNC和HSYNC是什么 VSYNC: vertical synchronization,指 ...