首先回顾一下MySql事务:
原子性,一致性,持久性,隔离性
下面看redis:
Redis事务特性
- 是否存在原子性?
这个特性存在争议,Redis没有回滚机制,多个操作批量执行,不能做到"⼀个失败就恢复到初始状态"
mysql的原子性:要么全部执行成功,要么都不执行
- 不存在一致性:
redis不涉及"约束". 也没有回滚.MySQL的⼀致性体现的是运⾏事务前和运⾏后,结果都是合理有效的,不会出现中间⾮法状态
- 不存在持久性:
是保存在内存的.是否开启持久化,是redis-server⾃⼰的事情,和事务⽆关
- 不存在隔离性:
redis单线程处理请求
Redis 事务本质上是在服务器上搞了⼀个"事务队列".每次客⼾端在事务中进⾏⼀个操作,都会把命令先发给服务器,放到"事务队列"中(但是并不会⽴即执⾏)
⽽是会在真正收到EXEC命令之后,才真正执⾏队列中的所有操作.
主要意义:
就是为了"打包",避免了其它客户端的命令插队插到中间
举个例子,我去烤串,我先点了几串,但是我朋友还没来,我告诉老板等一会再考,过了一会,朋友来了,又加了几个菜,我告诉老板,开始烤,此时我先点的菜和朋友后点的菜是一起烤的,这中间,是没有被插队的,这个不被插队,不是先抢占位置,而是先让出位置.
MULTI
开启⼀个事务.执⾏成功返回OK
EXEC
真正执行事务
每次添加⼀个操作,都会提⽰"QUEUED",说明命令已经进⼊客⼾端的队列了
真正执⾏EXEC的时候,客⼾端才会真正把上述操作发送给服务器
DISCARD
放弃当前事务.此时直接清空事务队列.之前的操作都不会真正执⾏到.
WATCH
在执⾏事务的时候,如果某个事务中修改的值,被别的客⼾端修改了,此时就容易出现数据不⼀致的问 题.
必须搭配事务使用,在multi之前
举例看一下:
客户端1:
客户端2:
最后客户端1,执行:
那么,此时key的值为多少?
从时间上看,先发送1,后发送2,结果是1,真正执行exec才会执行,1变成实际上更晚执行
这个时候,就会引起歧义
因此,即使不保证严格的隔离性,⾄少也要告诉⽤⼾,当前的操作可能存在⻛险
watch命令就是⽤来解决这个问题的
watch在该客⼾端上监控⼀组具体的key
当开启事务的时候,如果对watch的key进⾏修改,就会记录当前key的"版本号".(版本号是个简单 的整数,每次修改都会使版本变⼤.服务器来维护每个key的版本号情况).
在真正提交事务的时候,如果发现当前服务器上的key的版本号已经超过了事务开始时的版本号,就 会让事务执⾏失败.(事务中的所有操作都不执⾏).
此时,再按照上述的步骤执行,返回nil,说明明事务已经被取消了.这次提交的所有命令都没有执⾏
UNWATCH
取消对key的监控. 相当于WATCH的逆操作.此处不做演⽰
什么时候要使用Redis事务呢?
把多个操作打包进行,使用事务比较合适
比如说超卖现象:
放货3000,实际上3001个人下单成功,就超卖了
看一个典型的写法:
如果不加上任何限制,就可能存在线程安全问题
之前在多线程中,是通过加锁来避免插队的,在redis中就可以直接使用事务.
redis服务器收到执行事务操作的时候才会真正执行.
第二个客户端的执行事务命名发过来之后,服务器才真正执行第二个事务的内容,此时第一个事务已经运行了,那么第二个事务得到的个数就是第一个事务自减后的结果
此时,没加锁也可以解决超卖问题
补充:
乐观锁和悲观锁
不是具体的一个锁,而是一类
乐观锁:在加锁之前,就有一个预期,锁冲突概率低
悲观锁:锁冲突概率高
watch就是乐观锁