什么是事务
事务,在mysql中也有这样的概念。其中事务包括4个概念,原子性、一致性、持久性和隔离性。原子性就是将多个操作,打包成为一个整体执行,要么执行成功,要么执行失败。
一致性是指事务执行之后,数据依旧保持完整。
持久性 是在事务中的修改或写入都会存到硬盘中。
隔离性 是指在事务并发执行时,防止因交叉执行而导致数据不一致。
然后接下来是对比redis事务和mysql事务的不同。
弱化的原⼦性:关于原子性,mysql是将多个操作打包成一个操作,要么执行成功,要么执行失败。如果执行失败,就进行回滚操作,就是将事务操作中的已经执行的操作,全都进行回退。
而redis的 原子性是将多个操作打包成一个操作,要么全部执行,要么全都不执行。这里并没有和mysql一样保证执行成功,更没有mysql的回退机制。所以redis的原子性是一种弱化的原子性。
不保证⼀致性: redis事务不涉及 "约束". 也没有回滚. MySQL 的⼀致性体现的是运⾏事务前和运⾏后 , 结果都 是合理有效的, 不会出现中间⾮法状态。
不需要隔离性: 也没有隔离级别, 因为不会并发执⾏事务 (redis 单线程处理请求,所有请求/事务都是串行执行的) 。
不需要持久性: 是保存在内存的. 是否开启持久化, 是redis-server ⾃⼰的事情, 和事务无关。
Redis 事务本质上是在服务器上搞了⼀个 "事务队列"。 每次客户端在事务中进⾏⼀个操作,都会把命令先 发给服务器, 放到 "事务队列" 中(但是并不会⽴即执行) ⽽是会在真正收到 EXEC 命令之后, 才真正执行队列中的所有操作。
因此, Redis 的事务的功能相⽐于 MySQL 来说, 是弱化很多的. 只能保证事务中的这几个操作是 "连续 的", 不会被别的客⼾端 "加塞", 仅此⽽已。
redis事务命令
MULTI
开启⼀个事务. 执⾏成功返回 OK.

每次添加⼀个操作, 都会提⽰ "QUEUED", 说明命令已经进⼊客⼾端的队列了,但是并未被执行。
新开一个客户端,是查不到这样的数据的。

EXEC
真正执⾏事务。

执⾏ EXEC 的时候, 客⼾端才会真正把上述操作发送给服务器,执行队列中的操作。
在另外的客户端就能查看到设置的数据了

DISCARD
放弃当前事务。 此时直接清空事务队列。 之前的操作都不会真正执⾏到。

开启事务,并且给服务器发送若干个命令之后,此时服务器重启了。情况就等同discard的效果。
WATCH
在执行事务的时候, 如果某个事务中修改的值, 被别的客户端修改了, 此时就容易出现数据不⼀致的问 题。
watch必须搭配事务使用,并且必须在multi之前使用。
场景

从时间上来看,客户端1是先发送了set key 222,客户端2 是后发生了 set key 333
由于客户端中,是执行exec执行了,才会真正执行 set key 222,这个操作变成了实际上更晚执行的操作,最终值就是 222。
在上面的场景中,就可以使用watch命令来监控这个key,看看这个key在事务的multi和exec之间,是否在外部被其他客户端修改了。如果没修改了,给出提示信息
客户端1

客户端2在客户端1 set key 222之后,执行 set key 333,

然后在客户端1提交事务,再查看key

UNWATCH
取消对 key 的监控。相当于 WATCH 的逆操作
WATCH的实现原理
watch的实现,类似于一个"乐观锁"。乐观锁和悲观锁不是指具体的锁,而是指某一类锁的特性。
乐观锁:加锁之前,预期接下来锁冲突的概率比较低
悲观锁:加锁之前,预期接下来锁冲突的概率比较高
而锁的冲突概率高,和冲突概率低,所要做的工作是不一样的。C++、Linux涉及到的锁 mutex / std::mutex 都是悲观锁,Java synchronized 则是可以在乐观和悲观之间自适应的。
redis 的watch实现就相当于 基于版本号 的机制去实现乐观锁。

执行watch key的时候,就会给这个key安排一个版本号,并且记录下来。(这个版本号可以理解成一个"整数",这个整数在key每次修改的时候,都会"变大")。
在执行exec的时候,判定当前key的版本号和最初watch时的版本号是否一致,如果一致,说明当前key在事务开启到最终执行过程中,没有其他客户端修改,于是才能真正进行设置。如果不一致,不一致,说明key在其他客户端进行了修改,就直接丢弃事务中的操作,并且exec返回一个nil。也就是说watch本质是给exec加了个判定条件