• 使用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 发布!

    版权声明:本文为博主原创文章,未经博主允许不得转载。
  • 相关阅读:
    【LeetCode】Set Matrix Zeroes 解题报告
    CodeForces 14 E
    Linux守护进程的编程实现
    用fildder 查看loveuv 刷流量时通信的数据
    .NET MVC学习笔记(一)
    微价值:专訪《甜心爱消除》个人开发人员Lee,日入千元!
    JSP动作--JSP有三种凝视方式
    【Hibernate步步为营】--映射合集汇总
    阿里好的开源项目有哪些(善用工具)
    色彩搭配原理与技巧?
  • 原文地址:https://www.cnblogs.com/dtyy/p/13829276.html
Copyright © 2020-2023  润新知