1. 事务
1.1 Redis事务的概念:
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
- Redis事务没有隔离级别的概念:
批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。 - Redis不保证原子性:
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。. 事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
1.2 Redis事务的三个阶段:
- 开始事务
- 命令入队
- 执行事务
1.3 Redis事务相关命令:
watch key1 key2 ... :
监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
multi :
标记一个事务块的开始( queued )
exec :
执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
discard :
取消事务,放弃事务块中的所有命令
unwatch :
取消watch对所有key的监控
1.4 Redis事务使用案例:
案例一: 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> exec
1) OK
2) OK
3) OK
127.0.0.1:6379>
案例二: discard
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379>
案例三: 编译型异常
编译型异常时候,事务中所有的命令都不会被执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
案例四: 运行时异常
运行异常的时候,有错误的命令不运行,其他命令会运行。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> set k6 v6
QUEUED
127.0.0.1:6379> set k7 v7
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
127.0.0.1:6379>
案例五: 监控
- 悲观锁:无论做什么都加锁
- 乐观锁:什么都不加锁。大多数是基于数据版本(version)的记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个”version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。redis中可以使用watch命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。这样就可以对指定的key加乐观锁了。注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了exec,discard,unwatch命令都会清除连接中的所有监视。
情况1:正常执行
127.0.0.1:6379> set money 300
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> keys *
1) "money"
2) "out"
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> DECRBY money 20
(integer) 280
127.0.0.1:6379> incrby out 20
(integer) 20
127.0.0.1:6379>
情况二:两个客户端
客户端1:
127.0.0.1:6379> watch money #监控money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
#在这段时间,另外一个线程修改了money的值。
127.0.0.1:6379> exec
(nil)
#提交失败
客户端2:
127.0.0.1:6379> get money
"280"
127.0.0.1:6379> set money 400
OK
127.0.0.1:6379>