所谓事务(Transaction) ,是指作为单个逻辑工作单元执行的一系列操作
ACID回顾
1)Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部执行,要么全部不执行。
Redis:一个队列中的命令 执行或不执行
2)Consistency(一致性):数据库在事务执行前后状态都必须是稳定的或者是一致的。
Redis: 集群中不能保证时时的一致性,只能是最终一致性
3)Isolation(隔离性):事务之间不会相互影响。
Redis: 命令是顺序执行的,在一个事务中,有可能被执行其他客户端的命令的
4)Durability(持久性):事务执行成功后必须全部写入磁盘。
Redis有持久化但不保证 数据的完整性
Redis事务
1)Redis的事务是通过multi、exec、discard和watch这四个命令来完成的。
2)Redis的单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合。
3)Redis将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行
4)Redis不支持回滚操作
事务命令
multi:用于标记事务块的开始,Redis会将后续的命令逐个放入队列中,然后使用exec原子化地执行这个命令队列
exec:执行命令队列
discard:清除命令队列
watch:监视key
unwatch:清除监视key
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set s1 222
QUEUED
127.0.0.1:6379> hset set1 name zhangfei
QUEUED
127.0.0.1:6379> exec
1) OK
2) (integer) 1
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set s2 333
QUEUED
127.0.0.1:6379> hset set2 age 23
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
127.0.0.1:6379> watch s1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set s1 555
QUEUED
127.0.0.1:6379> exec # 此时在没有exec之前,通过另一个命令窗口对监控的s1字段进行修改
(nil)
127.0.0.1:6379> get s1
222
127.0.0.1:6379> unwatch
OK
事务机制
事务的执行
- 事务开始
在RedisClient中,有属性flags,用来表示是否在事务中
flags=REDIS_MULTI - 命令入队
RedisClient将命令存放在事务队列中
(EXEC,DISCARD,WATCH,MULTI除外) - 事务队列
multiCmd *commands 用于存放命令 -
执行事务
RedisClient向服务器端发送exec命令,RedisServer会遍历事务队列,执行队列中的命令,最后将执行的结果一次性返回给客户端。
如果某条命令在入队过程中发生错误,redisClient将flags置为REDIS_DIRTY_EXEC,EXEC命令将会失败返回。
typedef struct redisClient{
// flags
int flags //状态
// 事务状态
multiState mstate;
// .....
}redisClient;
// 事务状态
typedef struct multiState{
// 事务队列,FIFO顺序
// 是一个数组,先入队的命令在前,后入队在后
multiCmd *commands;
// 已入队命令数
int count;
}multiState;
// 事务队列
typedef struct multiCmd{
// 参数
robj **argv;
// 参数数量
int argc;
// 命令指针
struct redisCommand *cmd;
}multiCmd;
Watch的执行
使用WATCH命令监视数据库键
redisDb有一个watched_keys字典,key是某个被监视的数据的key,值是一个链表.记录了所有监视这个数据的客户端。
监视机制的触发
当修改数据后,监视这个数据的客户端的flags置为REDIS_DIRTY_CAS
事务执行
RedisClient向服务器端发送exec命令,服务器判断RedisClient的flags,如果为REDIS_DIRTY_CAS,则清空事务队列。
typedef struct redisDb {
// .....
// 正在被WATCH命令监视的键
dict *watched_keys;
// .....
}redisDb;
Redis的弱事务性
Redis语法错误
整个事务的命令在队列里都清除
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sets m1 44
(error) ERR unknown command `sets`, with args beginning with: `m1`, `44`,
127.0.0.1:6379> set m2 55
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get m1
"22"
flags=REDIS_DIRTY_EXEC
Redis运行错误
在队列里正确的命令可以执行 (弱事务性)
弱事务性 :
1、在队列里正确的命令可以执行 (非原子操作)
2、不支持回滚
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set m1 55
QUEUED
127.0.0.1:6379> lpush m1 1 2 3 #不能是语法错误
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get m1
"55"
Redis不支持事务回滚(为什么呢)
1、大多数事务失败是因为语法错误或者类型错误,这两种错误,在开发阶段都是可以预见的
2、Redis为了性能方面就忽略了事务回滚。 (回滚记录历史版本)