Redis 事务定义
Redis 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis 事务的主要作用就是串联多个命令防止别的命令插队。
Redis 事务特性
单独的隔离操作: 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念: 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
不保证原子性: 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚 。
Redis 事务命令
命令 | 描述 |
---|---|
multi | 标记一个事务块的开始。 |
exec | 执行所有事务块内的命令。 |
discard | 取消事务,放弃执行事务块内的所有命令。 |
watch key1 key2 … | 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
unwatch | 取消 WATCH 命令对所有 key 的监视。 |
事务的执行流程
从输入 Multi
命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入 Exec
后,Redis 会将之前的命令队列中的命令依次执行。组队的过程中可以通过 discard
来放弃组队。
事务的错误处理
组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
事务的测试实例
# ============================================================
# 正常执行
# ============================================================
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 命令入列
QUEUED
127.0.0.1:6379> set k2 v2 # 命令入列
QUEUED
127.0.0.1:6379> get k2 # 命令入列
QUEUED
127.0.0.1:6379> set k3 v3 # 命令入列
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
# ============================================================
# 取消事务
# ============================================================
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 22
QUEUED
127.0.0.1:6379> discard # 取消事务
OK
127.0.0.1:6379> get k2 # 数据未被改动
"v2"
# ============================================================
# 若在事务队列中存在语法性错误(类似于java的1/0的运行时异常)
# 则执行EXEC命令时,其他正确命令会被执行,错误命令抛出异常
# ============================================================
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 # 对k1(值为“v1”)进行+1
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> get k4
QUEUED
127.0.0.1:6379> exec # 执行事务,第一行命令报错,其他命令正常执行
1) (error) ERR value is not an integer or out of range
2) OK
3) "v4"
# ============================================================
# 若在事务队列中存在命令性错误(类似于java编译性错误)
# 则执行EXEC命令时,所有命令都不会执行
# ============================================================
127.0.0.1:6379> multi
OK
127.0.0.1:6379> getset k4 # 错误命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec # 执行事务,报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 命令未执行
(nil)
事务冲突的问题
假如有一个账户,同时去修改金额。
一个请求想给金额减8000
一个请求想给金额减5000
一个请求想给金额减1000
悲观锁解决方案
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁解决方案
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
watch 监控
在执行 multi
之前,先执行 watch key1 [key2]
,可以监视一个(或多个) key
,如果在事务执行之前这个(或这些) key
被其他命令所改动,那么事务将被打断。
unwatch
可取消 watch
命令对所有 key
的监视。如果在执行 watch
命令之后,exec
命令或 discard
命令先被执行了的话,那么就不需要再执行 unwatch
了。
测试实例
# ============================================================
# 初始化信用卡可用余额和欠额
# ============================================================
127.0.0.1:6379> set balance 100
OK
127.0.0.1:6379> set debt 0
OK
# ============================================================
# 使用watch检测balance,事务期间balance数据未变动,事务执行成功
# ============================================================
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec # 修改成功
1) (integer) 80
2) (integer) 20
# ============================================================
# 使用watch检测balance,事务期间balance数据变动,事务执行失败
# ============================================================
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi # 执行完毕后,执行窗口二代码测试
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec # 修改失败
(nil)
# 出现问题后放弃监视,然后重来
127.0.0.1:6379> unwatch # 放弃监视(此处也可省略,因为上面exec执行完之后,无论是否成功,监视都会被取消)
OK
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby debt 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 180
2) (integer) 40
# ============================================================
# 窗口二
# ============================================================
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> set balance 200
OK
说明:
一但执行 exec
开启事务的执行后,无论事务使用执行成功, warch
对变量的监控都将被取消。
故当事务执行失败后,需重新执行 warch
命令对变量进行监控,并开启新的事务进行操作。