欢迎光临
我们一直在努力

Redis命令:DECR

Redis DECR 命令详细教程

DECR 用于将指定 Key 存储的数字值减 1,并返回递减后的新值。它是 Redis 原子计数操作家族的一员,与 INCR、DECRBY、INCRBY 共同构成无锁计数方案的核心。由于命令在服务端原子执行,多个客户端并发递减同一 Key 时不会出现丢失更新,这是它相比"读取后回写"流程的最大价值。

本文基于 Redis String 数据类型介绍 DECR。Redis 命令本身不区分大小写,因此 DECR、decr 和 Decr 的效果相同;文档统一使用大写形式。DECR 自 Redis 1.0.0 起可用。

资料合集:https://pan.quark.cn/s/10e98d308913、https://pan.quark.cn/s/f56bc69c5338

一、命令概览

1. 基本语法

DECR key

参数说明:

参数说明
key 存储数字的目标 Key

返回值:

  • 返回递减操作完成后 Key 的新值(整数)。
  • Key 不存在时,先按 0 处理再递减,返回 -1 并创建该 Key。

DECR 没有指定递减幅度的参数,固定减 1;需要减去任意整数时使用 DECRBY。

2. 最简单的示例

先写入一个数字值:

SET stock:1001 10

递减一次:

DECR stock:1001

返回:

(integer) 9

再递减一次:

DECR stock:1001

返回:

(integer) 8

3. 命令特性

  • DECR 只作用于 String 类型的 Key,对其他类型返回 WRONGTYPE 错误。
  • 值必须能被解析为 64 位有符号整数,否则报错。
  • 时间复杂度为 O(1)。
  • 命令是原子操作:并发调用不会丢失任何一次递减。
  • 递减会保留 Key 已有的过期时间。
  • ACL 类别为 @write、@string、@fast。

二、DECR 的返回结果

1. Key 存在且值合法

SET attempts:1001 3
DECR attempts:1001

返回:

(integer) 2

返回的是新值,不是旧值,也不是递减的幅度。

2. Key 不存在

DECR fresh-counter

返回:

(integer) -1

Key 不存在时按 0 处理,0 – 1 = -1,同时 Key 被创建且值为 -1:

GET fresh-counter

返回:

"-1"

这一点容易被忽略:对不存在的 Key 执行 DECR 会产生负数计数器,而不是报错或返回空值。

3. 连续递减

DECR fresh-counter
DECR fresh-counter

依次返回:

(integer) -2
(integer) -3

4. 值不是整数

SET bad-value "abc"
DECR bad-value

返回:

(error) ERR value is not an integer or out of range

含小数点、空白字符或字母的值都会触发该错误:

SET float-value "10.5"
DECR float-value

同样返回 value is not an integer or out of range 错误。浮点数计数应使用 INCRBYFLOAT(用负数增量实现递减)。

空字符串同样报错:

SET empty-value ""
DECR empty-value

Key 存在但值为空串,不等于 Key 不存在,仍会报整数解析错误。

5. Key 的类型不是 String

RPUSH mylist a b
DECR mylist

返回:

(error) WRONGTYPE Operation against a key holding the wrong kind of value

可用 TYPE 命令确认实际类型后再选择对应命令。

6. 已过期的 Key

SET temp-counter 5 EX 1

等待超过 1 秒后执行:

DECR temp-counter

返回:

(integer) -1

已过期的 Key 被视为不存在,DECR 按 0 处理并重建 Key,返回 -1。新 Key 没有过期时间。

7. 到达数值下界溢出

SET min-value -9223372036854775808
DECR min-value

返回:

(error) ERR increment or decrement would overflow

64 位有符号整数的最小值是 -9223372036854775808,再减 1 会溢出,Redis 拒绝执行且不修改原值。

三、数值边界与内部编码

1. 64 位有符号整数范围

DECR 操作的数值范围是 64 位有符号整数:

边界值
最小值 -9223372036854775808(即 -2^63)
最大值 9223372036854775807(即 2^63 – 1)

DECR 只在下界溢出(详见第二章第 7 节);上界溢出只会发生在 INCR/INCRBY 场景。

2. int 编码

能表示为 long long 的字符串值在 Redis 内部使用 int 编码存储:

SET demo:counter 100
OBJECT ENCODING demo:counter

返回:

int

DECR 对 int 编码的值直接做整数运算,性能极高。值超出 long long 范围时会转为 embstr/raw 编码,此时再执行 DECR 会因无法解析为 64 位整数而报错。

3. 结果值的规范化

递减后的值以规范的整数文本存储。即使原值通过其他方式写入,只要能被解析为整数,DECR 之后的 GET 结果都是规范形式(无多余前缀)。

四、与相关命令的区别

1. DECR 与 INCR

两者方向相反,语法对称:

SET demo:counter 10
DECR demo:counter # 9
INCR demo:counter # 10

对比项DECRINCR
操作 减 1 加 1
Key 不存在时 从 0 开始,返回 -1 从 0 开始,返回 1
溢出方向 下界(LLONG_MIN) 上界(LLONG_MAX)

2. DECR 与 DECRBY

DECRBY 减去指定幅度,DECR key 等价于 DECRBY key 1:

SET demo:points 100
DECRBY demo:points 10
DECR demo:points

依次返回 90、89。

对比项DECRDECRBY
递减幅度 固定 1 任意 64 位整数
语法 DECR key DECRBY key decrement
负数幅度 不适用 相当于递增

注意 DECRBY 的幅度为负数时效果是加法:DECRBY key -5 等价于 INCRBY key 5。

3. DECR 与 INCRBY

INCRBY 增加指定幅度,用负数增量也能实现递减:

SET demo:counter 10
INCRBY demo:counter -3 # 7

INCRBY key -1 与 DECR key 效果相同。写代码时建议按语义选择:明确是"递减"就用 DECR/DECRBY,可读性更好。

4. DECR 与 INCRBYFLOAT

INCRBYFLOAT 操作浮点数,且只能"增"方向传参,递减靠负数增量:

SET demo:balance 10.5
INCRBYFLOAT demo:balance -1.5

返回 9。注意 INCRBYFLOAT 执行后值以浮点文本形式存储,之后再执行 DECR 会报"非整数"错误。整数计数与浮点计数不要混用于同一 Key。

5. DECR 与"GET 后 SET"

客户端自行实现的读取减法回写不是原子操作:

GET key -> 应用层减 1 -> SET key newvalue # 并发下互相覆盖,丢失更新
DECR key # 原子,无丢失

高并发计数场景必须使用 DECR 等服务端原子命令,或 Lua 脚本。

五、原子性、事务、Pipeline 与并发

1. 原子性

DECR 是单条命令,在 Redis 单线程模型中原子执行。任意时刻并发的 N 次 DECR,最终值恰好减少 N,任何一次都不会丢失。这是库存扣减、限流等场景信赖 Redis 计数的基础。

2. 在事务中使用

MULTI
DECR demo:counter
DECR demo:counter
EXEC

EXEC 返回各命令结果,如依次为 8、7(假设原值为 9)。事务内多条 DECR 按顺序执行。

需要"检查后再递减"的条件逻辑时,事务无法基于中间结果做分支判断,应使用 Lua 脚本(见下文)。

3. Pipeline 批量递减

对同一 Key 连续递减或对多个 Key 分别递减时,用 Pipeline 减少网络往返:

DECR quota:user:1
DECR quota:user:2
DECR quota:user:3

Pipeline 只负责批量传输,不改变各命令的原子性语义。

4. Lua 脚本实现有下界的递减

DECR 本身不限制结果为负。业务上通常要求计数不低于 0(如库存不为负),这时用 Lua 脚本把"递减 + 判断 + 回滚"变成原子操作:

EVAL "local v = redis.call('DECR', KEYS[1]) if v < 0 then redis.call('INCR', KEYS[1]) return -1 end return v" 1 stock:1001

脚本逻辑:先 DECR,若结果小于 0 则 INCR 还原并返回 -1 表示扣减失败;否则返回剩余值。这可以防止超卖、超扣等负数问题。

5. WATCH 乐观锁

除 Lua 外,也可用 WATCH 实现基于版本检查的并发控制,但计数场景下 Lua 脚本更简洁高效,WATCH 更适合多 Key 复杂条件的并发更新。

六、常见客户端用法

1. redis-cli

redis-cli SET stock:1001 10
redis-cli DECR stock:1001
redis-cli DECR stock:1001

两次 DECR 分别返回 9 和 8。

2. Python(redis-py)

import redis

client = redis.Redis(host="localhost", port=6379, decode_responses=True)

client.set("stock:1001", 10)
print(client.decr("stock:1001")) # 9
print(client.decr("stock:1001")) # 8

client.decr(key) 固定减 1;client.decrby(key, amount) 对应 DECRBY。

3. Node.js(node-redis)

import { createClient } from "redis";

const client = createClient();
await client.connect();

await client.set("stock:1001", 10);
console.log(await client.decr("stock:1001")); // 9
console.log(await client.decr("stock:1001")); // 8

await client.quit();

4. Java(Jedis)

import redis.clients.jedis.Jedis;

try (Jedis jedis = new Jedis("localhost", 6379)) {
jedis.set("stock:1001", "10");
System.out.println(jedis.decr("stock:1001")); // 9
System.out.println(jedis.decr("stock:1001")); // 8
}

各客户端的 decr 方法均返回递减后的新值(长整数)。

七、典型业务场景

1. 库存扣减

高并发抢购场景的库存余量扣减:

DECR stock:item:1001

配合第五章的 Lua 脚本可保证不超卖。注意单纯 DECR 允许减到负数。

2. 限流与令牌桶

单位时间窗口内的请求配额扣减:

DECR rate:user:1001

返回值大于等于 0 放行,小于 0 拒绝。配额重置由定时任务或 EXPIRE 完成。

3. 剩余次数控制

登录失败剩余次数、验证码重试次数:

DECR attempts:login:1001

减到 0 后锁定账户,比应用层数据库行锁更轻量。

4. 资源信号量

限制并发数的信号量获取:

DECR semaphore:pool

获取成功(结果 >= 0)则执行任务,完成后 INCR 归还。

5. 倒计时与配额追踪

活动名额、优惠券余量等单调递减指标的实时展示。

八、性能与使用建议

  • 时间复杂度为 O(1),int 编码下就是一次整数减法,单机可达极高 QPS。
  • DECR 是原子命令,任何需要并发计数的场景都应优先使用它而不是"GET 后 SET"。
  • 计数允许为负时要警惕:Key 不存在时 DECR 返回 -1 而不是报错,业务需显式初始化或校验。
  • 需要非负下界(库存、配额)时用 Lua 脚本封装"递减 + 溢出回滚"。
  • DECR 保留已有 TTL;由 DECR 新建的 Key 永不过期,需要时效时应补 EXPIRE。
  • 值必须是 64 位整数文本,浮点数使用 INCRBYFLOAT,且不要与整数计数混用同一 Key。
  • 高频写同一 Key 在集群模式会形成热 Key(所有请求路由到同一分片),必要时按 Key 加后缀分片计数、聚合读取。
  • DECR 是写命令,每次执行都会记录到 AOF 并复制到从节点,超高频率计数会放大持久化与复制流量,可考虑客户端聚合后批量提交。
  • 首次操作前用 SET key value NX 或业务初始化逻辑确保计数起点正确,避免"不存在按 0 处理"带来意外负数。
  • 九、常见问题排查

    问题 1:DECR 返回 -1

    最常见原因是 Key 不存在(按 0 减 1):

    EXISTS key
    GET key

    检查是否拼写错误、是否连接了错误的实例或逻辑数据库、Key 是否已过期。

    问题 2:报 “value is not an integer or out of range”

    Key 的当前值无法解析为 64 位整数。检查实际内容:

    GET key
    TYPE key
    STRLEN key

    常见原因:存了小数(10.5)、字符串(abc)、空字符串、含空白的值,或该 Key 曾被 INCRBYFLOAT 写成浮点文本。

    问题 3:报 WRONGTYPE 错误

    Key 是 List、Hash 等非 String 类型:

    TYPE key

    确认业务应改用对应类型的命令(如 HINCRBY),或先删除旧 Key 重建。

    问题 4:计数出现负数

    DECR 不做下界保护。需要非负语义时使用 Lua 脚本(见第五章第 4 节),或在应用层对返回值小于 0 的情况做补偿回滚(补偿不是原子操作,高并发下推荐脚本方案)。

    问题 5:报 “increment or decrement would overflow”

    值已是 64 位最小值 -9223372036854775808,再减会溢出。检查业务是否会把计数减到下界;正常业务不会触达该边界,出现时通常说明逻辑缺陷或恶意输入。

    问题 6:Key 的过期时间消失了

    DECR 本身保留 TTL。若发现 Key 变成永久,排查是否有不带过期选项的 SET 覆盖过该 Key(普通 SET 会清除 TTL),重新执行 EXPIRE 修正。

    十、完整练习

    下面的示例覆盖基本递减、Key 不存在、非法值、类型错误、溢出边界、TTL 保留与 Lua 防负数。请在测试数据库执行;FLUSHDB 会清空当前逻辑数据库中的全部 Key。

    FLUSHDB

    # 1. 基本用法
    SET demo:counter 10
    DECR demo:counter
    DECR demo:counter

    # 2. Key 不存在(按 0 处理)
    DECR demo:fresh
    GET demo:fresh
    DECR demo:fresh

    # 3. 非整数值报错
    SET demo:bad "abc"
    DECR demo:bad
    SET demo:float "10.5"
    DECR demo:float
    SET demo:empty ""
    DECR demo:empty

    # 4. 类型错误
    RPUSH demo:list a b
    DECR demo:list

    # 5. 下界溢出
    SET demo:min -9223372036854775808
    DECR demo:min
    GET demo:min

    # 6. TTL 保留
    SET demo:quota 5 EX 100
    TTL demo:quota
    DECR demo:quota
    TTL demo:quota

    # 7. 过期后递减重建
    SET demo:temp 5 EX 1
    # 等待 1 秒以上后执行:
    # DECR demo:temp
    # TTL demo:temp

    # 8. 与 DECRBY / INCRBY 对比
    SET demo:points 100
    DECRBY demo:points 10
    DECR demo:points
    INCRBY demo:points -3
    GET demo:points

    # 9. 与 INCR 对比
    INCR demo:points

    # 10. Lua 防负数递减
    SET demo:stock 1
    EVAL "local v = redis.call('DECR', KEYS[1]) if v < 0 then redis.call('INCR', KEYS[1]) return -1 end return v" 1 demo:stock
    EVAL "local v = redis.call('DECR', KEYS[1]) if v < 0 then redis.call('INCR', KEYS[1]) return -1 end return v" 1 demo:stock
    GET demo:stock

    # 11. 事务中的连续递减
    MULTI
    DECR demo:counter
    DECR demo:counter
    EXEC

    # 12. 内部编码
    SET demo:encoding 42
    OBJECT ENCODING demo:encoding
    DECR demo:encoding
    OBJECT ENCODING demo:encoding

    预期结果:

    • demo:counter 两次 DECR 依次返回 9、8。
    • DECR demo:fresh 返回 -1,GET 返回 "-1",再次 DECR 返回 -2。
    • demo:bad、demo:float、demo:empty 均返回 (error) ERR value is not an integer or out of range。
    • 对 List 执行 DECR 返回 (error) WRONGTYPE …。
    • demo:min 的 DECR 返回 (error) ERR increment or decrement would overflow,GET 确认值仍为 -9223372036854775808。
    • demo:quota 递减前 TTL 约 100,递减返回 4,TTL 继续递减(如 97)。
    • demo:temp 过期后 DECR 返回 -1,TTL 返回 -1。
    • demo:points:DECRBY 10 返回 90,DECR 返回 89,INCRBY -3 返回 86,GET 为 "86"。
    • INCR demo:points 返回 87。
    • demo:stock:第一次 EVAL 返回 0;第二次返回 -1(扣减失败并回滚),GET 返回 "0"。
    • 事务返回 7、6(基于步骤 1 后的值 8)。
    • demo:encoding 的编码为 int,递减返回 41 后编码仍为 int。

    十一、命令速查表

    需求命令
    减 1 DECR key
    减指定整数 DECRBY key decrement
    加 1 INCR key
    加指定整数 INCRBY key increment
    浮点数增减 INCRBYFLOAT key value
    Hash 字段计数 HINCRBY key field increment
    读取当前值 GET key
    判断是否存在 EXISTS key
    查询数据类型 TYPE key
    设置过期时间 EXPIRE key seconds
    原子条件递减 Lua 脚本(EVAL)

    总结

    DECR 的核心作用是将 Key 的整数值原子地减 1 并返回新值,基本语法为:

    DECR key

    使用时重点注意五点:

    • Key 不存在时按 0 处理,返回 -1 并创建 Key,不会报错;业务需防止意外的负数起点。
    • 值必须是可解析为 64 位有符号整数的文本,否则报 value is not an integer or out of range;浮点计数用 INCRBYFLOAT。
    • 原子性是核心价值,任何并发计数都应使用 DECR/INCR 家族而非"GET 后 SET"。
    • 递减保留已有 TTL,但新建的 Key 永不过期;需要非负下界(库存、配额)时用 Lua 脚本防止减成负数。
    • 固定减 1,减任意幅度用 DECRBY;下界 -9223372036854775808 再减会溢出报错且不修改原值。
    赞(0)
    未经允许不得转载:171主机测评 » Redis命令:DECR
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址