35

mysql中如何减库存

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzAwOTU4NzM5Ng%3D%3D&%3Bmid=2455771439&%3Bidx=1&%3Bsn=41d23957a883337362aa9993b05192a1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

《mysql事务到底解决什么问题》文中描述了注册代码的优化,但还没有上线,最近要做类似电商的项目,也用到了事务,其实也和秒杀业务有关。

比如先查询是否能秒杀一个商品,如果具备资格就减库存,并更新订单表,伪代码如下:

if (select * from table where 库存>已兑换人数 and 某个商品) {
    try {
        开启事务
        减库存
        更新订单表
        提交事务
    } catch {
        回滚事务
    }
}

有问题吗?有,外层虽然判断还有库存数,在并发量高的情况下,可能会导致兑换人数>库存数。

那如何修改呢?目前的方案:

if (select * from table where 库存>已兑换人数 and 某个商品) {
    try {
        开启事务
        $num = 减库存 update table set 兑换人数=兑换人数+1 where 库存>兑换人数
        if ($num ==0)
            回滚事务
        更新订单表
        提交事务
    } catch {
        回滚事务
    }
}

由于update是原子性的,在高并发的情况下,会保证兑换人数不会大于库存数,而事务是为了解决更新订单表和库存表更新是一致性的。

这个方案其实还可以,但感觉不是很优雅,是否还有其他方案?其实可以采用Locking Reads行锁。但本文只是基于理论,并没有实践。

对于行锁,官方文档说的非常透彻:

If you query data and then insert or update related data within the same transaction, the regular SELECT statement does not give enough protection. Other transactions can update or delete the same rows you just queried. InnoDB supports two types of locking reads that offer extra safety

有两种类型的行锁。

1:SELECT … LOCK IN SHARE MODE

在读的时候设置shared mode锁,那么其他事务能够读相应的记录,但如果要更新/删除相应的记录,必须等待你的事务提交完成。

对于这个订单库存更新的场景来说,容易出现死锁,官方是这样解释的:

Here, LOCK IN SHARE MODE is not a good solution because if two users read the counter at the same time, at least one of them ends up in deadlock when it attempts to update the counter.

2:SELECT … FOR UPDATE

为了解决shared mode死锁,可以采用for update锁这种模式:

For index records the search encounters, locks the rows and any associated index entries, the same as if you issued an UPDATE statement for those rows. Other transactions are blocked from updating those rows, from doing SELECT … LOCK IN SHARE MODE, or from reading the data in certain transaction isolation levels. Consistent reads ignore any locks set on the records that exist in the read view

通过for update锁,普通的select语句会等同于一个update语句,会锁住相应的行记录,其他读写操作必须等待这个事务提交后才能操作。

需要说明的是,这两种行锁都是在事务中生效的,事务提交或回滚后才会释放这两个锁。

对于锁,使用的越多,并发性就越低,最后贴下解决方案:

try {
    开启事务
    if (select * from table where 库存>已兑换人数 and 某个商品 for update) {
        减库存
        更新订单表
        提交事务
    } else {
        回滚事务
    }
} catch {
    回滚事务
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK