使用MySQL乐观锁解决超卖问题
在秒杀系统设计中,超卖是一个经典、常见的问题,任何商品都会有数量上限,如何避免成功下订单买到商品的人数不超过商品数量的上限,这是每个抢购活动都要面临的难点。
1 超卖问题描述
在多个用户同时发起对同一个商品的下单请求时,先查询商品库存,再修改商品库存,会出现资源竞争问题,导致库存的最终结果出现异常。
问题:当商品A一共有库存15件,用户甲先下单10件,用户乙下单8件,这时候库存只能满足一个人下单成功,如果两个人同时提交,就出现了超卖的问题。

可以采用多种方式解决超卖问题。使用synchronized可以保证数据一致性,但是效率低,并且分布式环境下无用;使用数据库锁表会造成数据库性能低下。单体条件下,采用乐观锁是比较合适的方式,集群可以考虑分布式锁。
2 乐观锁
2.1 乐观锁介绍
悲观锁,认为数据很容易被其他线程修改,为保证数据正确性,每次获取并修改数据时,对数据加锁。例如Java中的synchronized和Lock相关类。
而乐观锁,认为自己在操作时不会有其他线程干扰,所以不对被操作对象加锁。在更新时会判断修改期间是否有其他线程修改过。如果没被修改过,则表示只有当前线程在操作,正常修改数据。如果数据被其他线程修改过,则会停止刚才的更新,选择执行策略,例如抛弃、报错、重试等。
乐观锁一般使用CAS算法实现。例如Java中的原子类、并发容器。
2.2 没有锁的更新操作
乐观锁,不是数据库功能,是一种数据库实践。假设进行以下操作:从表中获取某行数据,计算数据,更新数据该行数据。
CREATE TABLE theTable(
iD int NOT NULL,
val1 int NOT NULL,
val2 int NOT NULL
)
INSERT INTO theTable (iD, val1, val2) VALUES (1, 2 ,3);
没有锁的处理
-- 查询数据
SELECT iD, val1, val2
FROM theTable
WHERE iD = @theId;
-- 计算新值
-- 更新数据
UPDATE
theTable
SET
val1 = @newVal1,
val2 = @newVal2
WHERE
iD = @theId;
-- 继续执行
2.3 乐观锁的实现方式1--条件控制
--查询数据
SELECT iD, val1, val2
FROM theTable
WHERE iD = @theId;
--计算新值
--更新数据
UPDATE
theTable
SET
val1 = @newVal1,
val2 = @newVal2
WHERE
iD = @theId
AND val1 = @oldVal1
AND val2 = @oldVal2;
--判断影响行数
-- {if AffectedRows == 1 }
-- {继续执行}
-- {else}
-- {数据过期}
-- {endif}
上面操作的关键在于,UPDATE指令的结构与后续受影响的行数检查,从而判断是否有人修改数据。上面所有操作没有使用事务,这也表明乐观锁的关键不在于事务本身。
2.4 扩展:事务的使用
--查询数据
SELECT iD, val1, val2
FROM theTable
WHERE iD = @theId;
--计算新值
--开始事务,更新数据
UPDATE
theTable
SET
val1 = @newVal1,
val2 = @newVal2
WHERE
iD = @theId
AND val1 = @oldVal1
AND val2 = @oldVal2;
--判断影响行数
-- {if AffectedRows == 1 }
-- COMMIT TRANSACTION; // 提交事务
-- {继续执行}
-- {else}
-- ROLLBACK TRANSACTION; // 回滚事务
-- {数据过期}
-- {endif}
使用了事务,便可以回滚修改。通过事务,我们可以确定每次回滚的操作量是多少,在何处放置事务边界以及在何处检查冲突。
对于其他进程在当前事务提交之前,会发生什么,取决于数据库当前的隔离级别。以SQL Server为例,其隔离级别是READ_COMMITTED,更新的行被锁定,直到COMMIT为止,因此“其他进程”无法对该行执行任何操作(保持等待状态),而SELECT(实际上只能执行READ_COMMITTED) 。
2.5 乐观锁的实现方式2--版本号
使用版本号,也是乐观锁常用实现方式。通过在表中增加一个version字段:读取数据时,将version字段值一并读出,数据更新一次,则version值加1。当我们提交更新时,判断表中最新的version值与之前读出的version值是否一致,如果一致,则更新,否则视为过期数据。

--查询数据
SELECT iD,val1,val2,VERSION
FROM theTable
WHERE iD = @theId;
--计算新值
UPDATE
theTable
SET
val1 = @newVal1,
val2 = @newVal2,
VERSION = VERSION + 1
WHERE
iD = @theId
AND VERSION = @oldversion;
--判断影响行数
-- {if AffectedRows == 1 }
-- {继续执行}
-- {else}
-- {数据过期}
-- {endif}
参考资料
https://stackoverflow.com/questions/17431338/optimistic-locking-in-mysql
本文由博客一文多发平台 OpenWrite 发布!
使用MySQL乐观锁解决超卖问题的更多相关文章
- redis分布式锁解决超卖问题
redis事务 redis事务介绍: 1. redis事务可以一次执行多个命令,本质是一组命令的集合. 2.一个事务中的所有命令都会序列化,按顺序串行化的执行而不会被其他命令插入 作用:一个队列 ...
- 使用mysql乐观锁解决并发问题
案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交.最后实际账户余额为1000 ...
- 使用mysql乐观锁解决并发问题思路
本文摘自网络,仅供个人学习之用 案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后 ...
- 使用mysql悲观锁解决并发问题
最近学习了一下数据库的悲观锁和乐观锁,根据自己的理解和网上参考资料总结如下: 悲观锁介绍(百科): 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持 ...
- MySQL 乐观锁与悲观锁
悲观锁 悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁. 悲观锁: ...
- mysql乐观锁总结和实践--转
原文地址:http://chenzhou123520.iteye.com/blog/1863407 上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任 ...
- 【转】MySQL乐观锁在分布式场景下的实践
背景 在电商购物的场景下,当我们点击购物时,后端服务就会对相应的商品进行减库存操作.在单实例部署的情况,我们可以简单地使用JVM提供的锁机制对减库存操作进行加锁,防止多个用户同时点击购买后导致的库存不 ...
- MySQL乐观锁在分布式场景下的实践
背景 在电商购物的场景下,当我们点击购物时,后端服务就会对相应的商品进行减库存操作.在单实例部署的情况,我们可以简单地使用JVM提供的锁机制对减库存操作进行加锁,防止多个用户同时点击购买后导致的库存不 ...
- mysql乐观锁总结和实践(转)
原文:mysql乐观锁总结和实践 上一篇文章<MySQL悲观锁总结和实践>谈到了MySQL悲观锁,但是悲观锁并不是适用于任何场景,它也有它存在的一些不足,因为悲观锁大多数情况下依靠数据库的 ...
随机推荐
- shell简介及变量的定义查看撤销
1.shell分类及相关软件 图形界面Shell(Graphical User Interface shell 即 GUI shell),如:GNOME.KDE 命令行式Shell(Command ...
- EF Core 一、重识 EF
重识EF EF Core 学习资料:https://docs.microsoft.com/zh-cn/ef/core/dbcontext-configuration/ 本为作为EF Core学习的开始 ...
- osd磁盘空间足够无法写入数据的分析与解决
前言 这个问题的来源是ceph社区里面一个群友的环境出现在85%左右的时候,启动osd报错,然后在本地文件系统当中进行touch文件的时候也是报错,df -i查询inode也是没用多少,使用的也是in ...
- Freebsd10.2安装包升级pkg引起环境破坏的解决
前言 freebsd10.2环境在安装一个新软件包的时候提示升级pkg到1.10.1,然后点击了升级,然后整个pkg环境就无法使用了 记录 升级完了软件包以后第一个错误提示 FreeBSD: /usr ...
- C++中STL的sort函数
简单介绍C++ sort函数 这个函数需要STL算法头文件 #include <algorithm> using namespace std; 这个sort( , , )可以带两个参数也可 ...
- SQL注入学习-Dnslog盲注
1.基础知识 1.DNS DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的 ...
- cookie和session的关系和区别
1.为什么会用到cookie和session 由于http请求是一种无状态的请求,一旦数据交换完毕便会关闭请求,再次交换数据则要再次发起请求,所以服务端无法通过连接追踪会话,确定用户身份,而cooki ...
- Codeforces Round #674 (Div. 3)
A 除一下就完了. 时间复杂度 \(O\left(t\right)\). B 分在对称线上的矩阵和不在对称线上的矩阵讨论. 时间复杂度 \(O\left(tn^2\right)\). C 肯定是先增加 ...
- 【CF983C】elevator——记忆化搜索
(题面来自luogu) 题意翻译 题意 一个9层的楼有一个可以容纳4个人的电梯,你要管理这个电梯. 现在各层楼上有一些在排队的人,你知道他们在哪层要到哪层去.你也知道到电梯门口的顺序.根据公司的规定, ...
- Java基础教程——缓冲流
缓冲流 "缓冲流"也叫"包装流",是对基本输入输出流的增强: 字节缓冲流: BufferedInputStream , BufferedOutputStream ...