Redis自增到底得不能加锁,操作时会不会有冲突问题呢?
- 问答
- 2026-01-16 17:32:46
- 1
在绝大多数常规使用场景下,Redis的自增(INCR、INCRBY等)命令是原子性的,因此不需要额外加锁,也不会产生冲突问题。 你可以放心地让成千上万个客户端同时去对同一个键进行自增操作,Redis会保证每个操作都会被顺序、正确地执行,最终结果一定是准确的。
为什么Redis的自增操作可以不加锁?
这要从Redis本身的特性说起,Redis是一个单线程架构的键值数据库(这里指其核心的网络I/O和键值对读写是由一个线程串行处理的),这意味着,在任何给定的时刻,Redis只会执行一个命令,当多个客户端同时发送INCR命令给Redis服务器时,这些命令会进入一个队列,Redis会按照接收到的顺序,一个一个地执行。
想象一下银行的一个柜台,只有一个柜员,即使外面排了很长的队(多个客户端请求),每个人(每个命令)都是单独走到柜台前办理业务,当柜员在处理一个人的“存款”或“取款”业务时,他不会被打断,同样地,当Redis在执行一个INCR命令时,它会完整地完成“读取当前值、增加一、写回新值”这一系列动作后,才会去处理下一个命令,这个过程是“原子性”的,即不可分割,所以中间不会被其他命令插入。
Redis的单线程模型天然地避免了并发冲突,冲突通常发生在多线程或多进程环境下,当一个线程读取了某个值,但还没来得及修改时,另一个线程也读取了旧值并进行修改,从而导致数据不一致,在Redis这里,由于命令是串行执行的,根本不会出现“同时读取旧值”的情况。
在什么情况下可能需要考虑“锁”或冲突呢?
虽然单个INCR命令是安全的,但当我们把多个命令组合在一起,试图实现一个更复杂的逻辑时,原子性就无法保证了,这时冲突问题就会浮现,这种情况通常发生在“先读后写”的业务场景中。
举一个经典的例子:库存扣减。
假设我们有一个商品库存,键为 stock:item001,初始值为10,我们想实现的逻辑是:如果库存大于0,则将其减一。
一个新手可能会用以下步骤实现:
GET stock:item001// 读取当前库存,比如返回10。- 在应用程序中判断:10 > 0,则执行下一步。
DECR stock:item001// 库存减一。
在单线程环境下没问题,但在高并发场景下,问题就大了,假设现在有两个请求A和B同时到来:
- 请求A执行了第1步,GET到库存为10。
- Redis去处理请求B了,请求B也GET到了库存为10。
- 请求B判断库存大于0,然后执行了DECR,库存变成了9。
- 现在Redis回来继续处理请求A,请求A基于它之前读到的旧值10进行判断(10>0),也执行了DECR,库存变成了8。
结果就是,原本10个库存,在两个请求之后变成了8,相当于只卖出了一件商品却扣减了两件库存,这就是典型的“超卖”问题,在这种情况下,冲突发生了。
如何解决这种复杂操作的冲突?
对于这种需要多个命令组合才能完成的业务逻辑,Redis提供了几种机制来保证原子性,避免冲突:
-
使用Lua脚本:这是最常用、最推荐的方案,Redis允许你将多个命令写在一个Lua脚本中,然后一次性发送给Redis执行。整个Lua脚本在执行时会被当作一个独立的命令,是原子性的,在执行过程中不会被其他命令打断,对于上面的库存扣减例子,我们可以写一个Lua脚本,里面包含读取库存、判断和扣减的逻辑,这样,两个请求过来时,它们的脚本会排队执行,第一个脚本执行完毕(库存变为9)后,第二个脚本读取到的库存就是9,如果库存不足就可以直接返回失败,从而避免超卖。
-
使用事务(MULTI/EXEC):Redis的事务可以打包一批命令,然后顺序执行,但是需要注意,Redis的事务并不是严格意义上的原子事务(像数据库那样),它只是保证了这批命令会连续执行,中间不会插入其他命令,但它不提供回滚机制,并且在事务内部,命令是放入队列,直到EXEC时才真正执行,期间无法感知键值的变化,为了解决这个问题,Redis提供了WATCH命令,WATCH可以监视一个或多个键,如果在事务执行之前,被监视的键被其他客户端修改了,那么整个事务都会失败,这相当于一种乐观锁机制,在上面的例子中,我们可以在MULTI之前先WATCH库存键,然后GET,判断,再DECR,如果期间库存被其他客户端改动,我们的事务会失败,然后我们可以选择重试或放弃,相比于Lua脚本,WATCH+MULTI的方式稍显复杂,Lua脚本通常是更优选择。
-
使用Redis的分布式锁:这是一个更重量级的方案,你可以通过SET命令的NX参数(SET key value NX PX timeout)来实现一个分布式锁,客户端在操作之前先尝试获取锁,只有拿到锁的客户端才能执行后续的“先读后写”操作,操作完成后释放锁,这种方式能保证强一致性,但因为涉及加锁、解锁,性能开销较大,一般只在Lua脚本难以实现的非常复杂的业务场景下才考虑。
- 对于单一的INCR、DECR、SETNX等命令:你完全不用担心,Redis的原子性为你保驾护航,无需加锁。
- 对于需要多个命令组合的复杂逻辑:冲突风险是真实存在的,你应该优先使用 Lua脚本 来保证原子性,如果场景特殊,再考虑事务(WATCH+MULTI) 或分布式锁。
回到最初的问题,答案很明确:Redis自增本身无冲突,无需锁;但你的业务逻辑如果超出了单个命令的范围,就需要你主动利用Redis提供的工具(主要是Lua脚本)来规避冲突了。

本文由邝冷亦于2026-01-16发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/81920.html
