Power谭的抖音 - 抖音
https://www.douyin.com/user/self?modal_id=7155471953803955460&showTab=favorite_collection
version是好,但是意味着同一时刻只能有一个人能购买商品,假设库存有1000,这个时候1000个用户同时购买,应该都成功,但是通过version就会导致只有1个成功,999个失败,因为版本不一致
用stock很不好,假如第三条线程进来把数据改回了最初的值,实际上此stock已经非彼stock了。虽然这个秒杀场景可以,但一旦涉及可以修改回最初值的业务,这个做法就不安全了 还是version最好
这个 SQL 语句是在更新 t_sku
表中的 stock
字段,使其减少 1,同时要求 id
为 1 且 stock
大于 0。
这样的 SQL 语句在多线程并发情况下是有潜在问题的,主要原因是没有采取任何形式的并发控制,可能导致超卖的问题。在多个线程同时执行这个 SQL 语句时,可能会出现以下情况:
-
读脏数据: 一个线程读取
stock
值,然后另一个线程也读取相同的值,接着两个线程都尝试执行减少操作。由于没有在读和写之间进行任何同步控制,可能会导致一个线程写入的结果被另一个线程读取,从而产生脏数据。 -
并发更新: 多个线程同时执行更新操作,可能会导致竞争条件,使得多个线程都执行成功,导致实际更新的数量超过实际库存数量,从而出现超卖的问题。
为了解决这些并发问题,你可以考虑使用数据库的事务和乐观锁机制来确保操作的原子性和一致性。这通常包括以下几个步骤:
-
启用事务: 将更新操作放在事务中,确保要么全部成功,要么全部回滚。
-
使用乐观锁: 在
WHERE
子句中添加版本号或者其他适合的条件,以确保在更新时对记录进行版本控制,防止并发更新导致的问题。
下面是一个简单的使用乐观锁的例子,假设 version
字段是用于版本控制的:
UPDATE t_sku
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND stock > 0 AND version = ?;
在这个例子中,version
字段用于确保并发更新时的一致性,每次更新
继续上面的例子,如果使用乐观锁并且希望在更新失败时进行重试,可以在应用层代码中实现类似如下的逻辑:
public class SkuService {
@Autowired
private SkuRepository skuRepository;
public boolean purchaseSku(int skuId) {
for (int retryCount = 0; retryCount < MAX_RETRY_COUNT; retryCount++) {
try {
// 获取当前SKU信息
Sku sku = skuRepository.findById(skuId);
// 检查库存是否足够
if (sku.getStock() <= 0) {
return false; // 库存不足
}
// 尝试更新库存
int updatedRows = skuRepository.updateStockWithOptimisticLock(skuId, sku.getVersion());
// 如果更新成功,则表示购买成功
if (updatedRows > 0) {
return true;
}
} catch (OptimisticLockingFailureException ex) {
// 乐观锁失败,重试
// 可以记录日志或者执行其他操作
}
}
return false; // 重试次数超过上限,购买失败
}
}
在上述代码中,purchaseSku
方法尝试多次购买,每次都从数据库加载最新的 SKU 信息,检查库存是否足够,然后尝试更新库存。如果在更新时发现版本不匹配(OptimisticLockingFailureException
异常),说明乐观锁失败,可以选择进行重试。你可以根据实际需求调整重试的策略。
此外,数据库表中需要包含一个用于版本控制的字段(在上述例子中是 version
)。在执行更新时,需要确保 WHERE
子句中包含版本号,并在更新时递增版本号。这样可以保证并发更新时的一致性。
最简单的做法,秒杀前预热,根据用
户的购买记录,浏览
记录生成和账号绑定的一个用于购买的key,比如商品有1000个,就生成10000个key【这个比例需要好好设计】,然后将这个key保存到前端的app
如果后续购买时没有这个key直接不往后台发送请求,假装等待一段时时间后直接返回失败[看],然后剩下的请求分为正常到服务器和有人调接口进服务器,将所有没有key或者key不合理的全部直接拒绝。
然后将剩余的请求放入多个队列,然后根据库存生成分段,比如商品1000个,分为10个段,orderid-1到orderid-10几个分布式锁,谁拿到锁谁就买,库存啥的直接在redis减,后续同步到数据库
说简单到就是提前筛选客户和分段加锁。
UPDATE t_sku SET stock = stock - 购买数量 WHERE id = 1 AND stock - 购买数量 >= 0 ; 这样有并发问题吗 会超卖吗,不查询 直接UPDATE
使用 UPDATE
语句时,不进行查询而直接更新是存在并发问题的,并且可能导致超卖问题。在多线程或多用户并发情况下,两个或多个线程可能同时执行 UPDATE
语句,而不会互相感知对方的操作,从而导致出现超卖的情况。
考虑以下情况:
- 线程A和线程B同时执行
UPDATE
语句。 - 线程A读取了
stock
值,假设为10,而线程B也读取了相同的值10。 - 线程A执行
UPDATE
将stock
减去购买数量,更新后的值为5。 - 线程B执行
UPDATE
将stock
同样减去购买数量,更新后的值也为5。
在这种情况下,两个线程分别减去了相同的购买数量,导致实际库存为负数,出现了超卖的问题。
为了解决这个并发问题,你可以使用数据库的事务和乐观锁机制。这样可以确保 SELECT
和 UPDATE
操作是原子的,并且在事务中使用乐观锁来控制并发更新。在使用乐观锁时,需要在 UPDATE
语句中增加一个版本号或者其他适当的条件,以确保在更新时对记录进行版本控制,防止并发更新导致的问题。
示例代码可能如下:
-- 初始化版本号为1
UPDATE t_sku SET version = 1 WHERE id = 1;
-- 购买操作,使用乐观锁
UPDATE t_sku
SET stock = stock - 购买数量,
version = version + 1
WHERE id = 1 AND stock - 购买数量 >= 0 AND version = ?;
在上述例子中,通过 version
字段来进行乐观锁控制,确保在并发更新时只有一个事务能够成功执行更新操作。
还是不理解为什么 有并发问题 会超卖 ,请详细解释
让我们详细解释一下为什么在不进行查询直接更新的情况下,可能导致并发问题和超卖的情况。
假设有一个商品表 t_sku
,其中有两个字段:id
和 stock
。现在考虑以下场景:
- 商品A的初始库存是10。
- 两个用户同时想购买商品A,每个用户购买数量为5。
使用如下 SQL 语句来进行更新:
-- 用户1的操作
UPDATE t_sku SET stock = stock - 5 WHERE id = 1 AND stock - 5 >= 0;
-- 用户2的操作
UPDATE t_sku SET stock = stock - 5 WHERE id = 1 AND stock - 5 >= 0;
现在,让我们考虑并发执行这两个操作的可能情况:
- 用户1的操作读取
t_sku
表,发现stock
是10,然后执行更新,stock
变成了5。 - 同时,用户2的操作也读取
t_sku
表,发现stock
也是10,然后执行更新,stock
变成了5。
在这个过程中,由于两个用户同时执行了更新操作而不进行查询,它们都基于相同的初始 stock
值(10)执行了减法操作。这就导致了一个问题:实际库存应该是10-5-5=0,但由于两个操作并发执行,最终库存变成了5。
这就是超卖的情况,因为实际库存小于用户购买数量的总和。这种问题的根本原因是没有进行并发控制,多个用户同时执行更新操作,导致了竞争条件。为了避免这种情况,可以使用事务和乐观锁机制,确保更新是原子的,并且在更新时对记录进行版本控制,以防止并发更新导致的问题。