fiber框架本身无法防止超卖,关键在于底层存储的原子操作:mysql需用带条件的原子update语句并检查rowsaffected,redis须用lua脚本封装get与decrby,禁用分布式锁处理高并发秒杀,且需异步对账补偿redis与db库存不一致。

直接用 Fiber 框架本身防不了超卖——它只是 HTTP 路由和中间件层,不提供库存原子操作能力。真正起作用的是你调用的底层存储(MySQL / Redis)和扣减逻辑是否原子。关键不在框架,而在你怎么写那条 SQL 或 Lua 脚本。
MySQL 扣减必须用原子 UPDATE,别碰 SELECT + UPDATE
很多人在 Fiber 里写:
app.Post("/buy", func(c *fiber.Ctx) error {
stock := db.QueryRow("SELECT stock FROM products WHERE id = ?", pid).Scan(&s)
if s > 0 {
db.Exec("UPDATE products SET stock = stock - 1 WHERE id = ?", pid)
}
})
这在并发下必然超卖。两个请求同时读到 stock = 1,都进 if,都执行 update,最终变成 -1。
- 正确做法:把判断和扣减压进一条
UPDATE,靠 MySQL 行锁+条件判断保证原子性 - SQL 必须带
stock >= ?,不能只写stock > 0(比如要扣 3 件,库存剩 2 就不该通过) - 必须检查
Result.RowsAffected(),为 0 就是库存不足或被抢光 -
Fiber中只需原样执行该 SQL,不用额外加事务(单条 UPDATE 本身就是事务)
Redis 预扣减必须用 Lua,禁用 GET+DECRBY 组合
用 Redis 抗并发时,常见错误是:
stock := c.Redis.Get("stock:1001").Val()
if stock > "0" {
c.Redis.Decr("stock:1001") // ❌ 两步分离,毫秒级间隙就是超卖窗口
}
100 个并发全读到 "10",全进 if,全 decr,结果扣成 -90。
- 必须用
Redis.Eval()执行 Lua 脚本,把GET和DECRBY封在一个原子操作里 - 脚本里要显式判断
tonumber(stock) >= tonumber(ARGV[1]),再决定是否扣减 - 返回值必须是整数(如 1 成功 / 0 失败),
Fiberhandler 根据这个做业务分支 - 别依赖
LLEN或GET的结果做 if 判断——它们不是原子读
Fiber 中的分布式锁只适合低频场景,别用于秒杀主链路
有人在 Fiber 路由里加 redisson.Lock().tryLock() 或自研 Redis SETNX 锁,然后查库、扣减、释放——这本质是串行化,并发上限直接受锁获取延迟拖累。
- QPS 过 500 就开始排队,秒杀时大量请求卡在
tryLock()超时失败 - 锁过期时间难设:设短了可能业务没做完锁就丢了;设长了故障时锁堆积
- 如果扣减成功但后续订单落库失败,锁释放了,库存却没回滚 → 少卖
- 真正该用锁的地方是「库存回滚」或「超时释放」这类低频补偿操作,不是下单主流程
最易被忽略的一点:Redis 预扣成功后,数据库落库失败(比如网络抖动、事务冲突),会导致 Redis 库存虚减、DB 库存未变——这个不一致状态必须有异步对账任务兜底,不能只靠一次 Lua 脚本就认为“已扣完”。











