标签 事务 下的文章

Redis 事务

Redis 的事务不同于关系型数据库, 事务模型很不严格, 我们对比着 MySQL 来看

基本用法

命令分为:

  • multi 事务的开始 (类似 MySQL 的 begin)
  • exec 执行事务 (类似 MySQL 的 commit)
  • discard 丢弃事务不执行 (类似 MySQL 的 rollback)
// exec
127.0.0.1:6379> get jw
(nil)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set jw 666
QUEUED
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get jw
"666"

这里要注意, 如果 exec返回非 OK (如nil )则表示执行失败

// discard
127.0.0.1:6379> get jw
(nil)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set jw 888
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get jw
(nil)

exec之前的命令都被缓存在事务队列里并为执行, QUEUED是一个简单的字符串, 表示指令已成功缓存到事务队列里.

原子性

文首我们说了 Redis 的事务模型不严格, 主要是指原子性, 请看下面代码

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set jw_str hehe
QUEUED
127.0.0.1:6379> incr jw_str
QUEUED
127.0.0.1:6379> set jw2 666
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> get jw2
"666"

可以看出事务执行到第二个指令失败了(不能对字符串做+1的数学运算), 但是第三个指令仍然被执行. 所以 Redis 事务根本不具备原子性, 只仅仅满足了事务"隔离性"中的串行化--当前执行的事务不被其他事务打断.

watch

考虑一个业务场景, Redis 存储了账户余额, 现在需要对余额增减, 流程是先把值取出来, 修改后再写回去.

这时如果有多个客户端操作这个 key 就会出现并发问题, 而 Redis 提供的 watch 指令就可以解决这个问题., 使用方式如下:

127.0.0.1:6379> get jw
(nil)
127.0.0.1:6379> watch jw
OK
127.0.0.1:6379> set jw 666
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set jw 888
QUEUED
127.0.0.1:6379> exec
(nil)

从上面代码示例可以看出, watch 会在事务开始之前就盯住一个变量, Redis 在执行 exec 时会检查变量自 watch 后是否有被修改过, 包括当前客户端在内, 如果被修改过, 则事务执行失败.

注意, Redis 禁止在multiexec 之间执行 watch 指令, 必须在事务开启指令 multi 之前盯住变量