事务
Redis事务本质是一组命令的集合,按队列执行。事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。
Redis事务没有隔离级别的概念,Redis单条命令是保证原子性的,但是事务不保证原子性!
Redis的事务操作非常简单,分为下面3步:
- 开启事务
multi
- 命令入队
...
- 执行事务
exec
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> set k3 v3
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> EXEC # 执行事务
1) OK
2) OK
3) OK
4) "v1"
5) 1) "k3"
2) "k2"
3) "week"
4) "k1"
取消事务discard
127.0.0.1:6379> FLUSHDB # 清空当前数据库
OK
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> DISCARD # 取消事务
OK
127.0.0.1:6379> EXEC # 执行事务 报错 没开启事务
(error) ERR EXEC without MULTI
127.0.0.1:6379> get k1 # get k1 发现为空 说明根本没有被执行
(nil)
127.0.0.1:6379> get k2
(nil)
当执行事务的时候也会出现错误,基本是下面两种错误:
1、命令的语法错误(编译时异常)所有的命令都不执行
127.0.0.1:6379> FLUSHDB # 清空当前数据库
OK
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v3
QUEUED
127.0.0.1:6379> get k1 k2 # 这个命令明显是错误的,会在你输入的时候报错,单不影响你下面的命令入队
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379> EXEC # 执行失败
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1 # 为空,说明没有被执行
(nil)
2、命令逻辑错误 (运行时异常) 其他命令可以正常执行
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> set k1 "v1" # set一个字符串
QUEUED
127.0.0.1:6379> INCRBY k1 1 # 字符串加一肯定是错的,但是语法是对的
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> EXEC # 执行
1) OK
2) (error) ERR value is not an integer or out of range # 报错
3) "v1"
127.0.0.1:6379> get k1 # 尽管执行的时候单条命令报错,但是其他的命令还是执行了,所以redis事务不保持原子性
"v1"
乐观锁
- 获取version,更新version
- 在redis里面用watch来实现
正常执行
127.0.0.1:6379> FLUSHDB # 清空当前数据库
OK
127.0.0.1:6379> set A 100 # A用户有100元
OK
127.0.0.1:6379> set use 0 # 使用了 0 元
OK
127.0.0.1:6379> WATCH A # 用watch监视 A 加锁
OK
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> DECRBY A 10 # A用户使用了10元
QUEUED
127.0.0.1:6379> INCRBY use 10 # use理所当然的加10元
QUEUED
127.0.0.1:6379> EXEC # 执行 没问题
1) (integer) 90
2) (integer) 10
模拟多线程执行插队
线程一
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set use 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY use 20
QUEUED
线程一还没有执行,但是线程二在线程一执行前修改了money的数据
127.0.0.1:6379> INCRBY money 20
(integer) 120
然后线程一执行
127.0.0.1:6379> EXEC # 执行失败
(nil)
注意:每次EXEC
执行完毕后,会自动释放锁,也可以以防万一用UNWATCH
手动解锁