背景
在日常生活中,涉及购买东西的场景下,卖方需要自己有一个记账本,记录物品和价格以及购买记录的一些信息。假设有一个价格管理系统,记录了每个物品对应的价格和购买次数,以及这些物品什么时候被买走。有两张表,一张是物品价格表goods_price,一张是购买记录表buy_record。
goods_price
id | good_name | buy_count | stock |
---|---|---|---|
1 | 铅笔 | 2 | 10 |
buy_record
id | good_name | buy_time | buyer |
---|---|---|---|
1 | 铅笔 | 2021-09-15 | jack |
2 | 铅笔 | 2021-08-15 | tom |
每当有人买东西的时候, buy_record就新增一条记录,并且 goods_price 中的购买次数会增加;有人退东西的时候,buy_record就删除一条记录,并且 goods_price 中的购买次数会减少。
每个物品有一定数目的库存stock,卖出的数量不能超过这个库存数,也不能小于零。
遇到的问题
如果到了开学的时候,铅笔的购买量变大,同一时间有很多人都在购买铅笔,也有人退掉不满意的铅笔,那么 goods_price 中的购买次数这个字段,就有可能由于并发操作而发生数据错误。
比如两个人同时抢购最后一根铅笔,库存显示都是1,此时他们同时下了单,卖出的次数就超过了库存数,就发生了数据错误。
(问题延伸:如果每个人只能买一次,不可以短时间内重复下单/由于网络原因导致重复请求,怎么解决?【考虑在数据库中判断购买记录是否重复】)
解决方案
思路一:事务
首先要确定的是,两个表的操作是一起的,购买记录多一条,购买次数也要+1,他俩要么都成功,要么都失败。所以这两个表的操作要在一个事务中进行。中途任何一个操作失败,都需要回滚。
//伪代码
transaction.begin();
record.insert();
if (failed) transaction.rollback();
price.update();
if (failed) transaction.rollback();
transaction.commit();
存在的问题:事务本身不能解决并发情境下的数据不一致问题,因为两个事务可以同时begin,也可以同时commit,仍然会有之前说的超卖问题。加上事务,只是保证了两张表之间的操作连续起来,不会出现两个表之间的数据不一致,是解决并发带来问题的第一步。
思路二:表锁
因为涉及对表字段的更新,所以考虑对price表加表锁。当一个事务在对其中某条记录进行更新时,别的事务不能写这张表。等于是将并发事务变成了串行的,可以彻底解决并发问题。
存在的问题:表锁的开销太大,效率太低。如果并发量变大,那么等待释放表锁的时间会很长。
思路三:行锁
只对涉及的某一行进行加锁,更新操作。这样可以避免因为锁表带来的性能下降问题。
存在的问题:锁行的方式,由于访问行顺序不同,有可能产生互相等待的死锁。
解决方法:按照id的升序进行加行锁。
思路四:redis锁
redis锁在大量并发的情况下,可以提高访问数据库的效率。要根据业务场景考虑是否有必要加redis锁。
注意并发和高并发之间的量级区别。
同类型文章:
高并发场景下的解决方案以及分布式锁的实现