Redis(四)——事务
说到事务我想大家都不陌生,因为我们日常中对事务的使用还是蛮场景的,最常见的就是mysql的事务,也就是我们关系型数据库的事务,那么redis这个非关系型数据库在事务方面和myql这个关系型数据库有什么区别呢?
redis事务和mysql事务对比
特性 | Redis事务 | MySQL事务 | 本质差异 |
|---|---|---|---|
ACID特性 | 部分支持(无隔离性) | 完全ACID支持 | 根本区别 |
隔离级别 | 无隔离性 | 4种隔离级别 | 核心差异 |
执行方式 | 批量执行 | 实时执行 | 行为差异 |
回滚机制 | 无回滚(仅取消) | 完整回滚 | 关键区别 |
锁机制 | WATCH命令(乐观锁) | 悲观锁(行锁、表锁) | 并发控制 |
使用场景 | 批量操作、简单原子性 | 复杂业务、数据一致性 | 定位不同 |
从上面的表可以看出redsi的事务其实是mysql的阉割版本,没有隔离性,还记得mysql的隔离性是由什么构成的吗?没错就是锁机制和MVCC来保障隔离性的。
但是Redis和mysql最大的区别就是,redis是一个单线程架构,也就是所有的命令都是排队进行的,所以就不用像mysql一样,因为根本就不存在什么脏读、幻读的问题,但是还是有锁的概念的,因为redis只是命令是单线程的,并不是业务是单线程的。如下:
# 问题场景:余额检查+扣款(非原子操作)
# 客户端A
127.0.0.1:6379> GET balance:user1
"100"# 此时客户端B也读取余额
127.0.0.1:6379> GET balance:user1
"100"# 客户端A扣款
127.0.0.1:6379> DECRBY balance:user1 60
(integer) 40# 客户端B也扣款(但余额已不足!)
127.0.0.1:6379> DECRBY balance:user1 60
(integer) -20 # 余额变为负数!数据不一致!# 这就是著名的"超卖"问题所以redis也是需要锁的,而这个锁的粒度就没有mysq分那么多了,只有一个键锁,顾名思义就是把键锁住,而且是乐观锁,只要不修改,查询什么的都不受到影响。如下:
# Redis的乐观锁机制
# 客户端A
127.0.0.1:6379> WATCH balance:user1 # 开始监控
OK
127.0.0.1:6379> GET balance:user1
"100"
127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379> DECRBY balance:user1 50
QUEUED# 此时客户端B修改了监控的key
127.0.0.1:6379> DECRBY balance:user1 10 # 客户端B的操作
(integer) 90# 客户端A执行事务
127.0.0.1:6379> EXEC #执行事务
(nil) # 事务执行失败!因为WATCH的key被修改# 这就是Redis的乐观锁:检测到冲突则放弃执行事务的特性
redis事务的特点就是批处理,我们使用multi命令开启事务后,其他会话的命令就可以执行了,因为redis是命令单线程的,所有的命令都是排队执行的。而multi命令只是在当前会话开启了一个队列,然后把需要一批执行的命令放入其中,在使用exec命令去执行这批命令,而这个时候事务中命令没执行完其他会话是被阻塞的。所以被叫做批处理。另外其他会话是不能执行这个事务的,每个事务在每个会话都是单独的。
另外就是redis 的事务是没有回滚的,也就是说只要加入事务的时候没错,加入事务的时候没有语法问题,就能够执行,执行成功和执行失败事务不管,他只保障这一批命令是一起执行的。如果放入事务的时候命令被检查出错了,那么整个事务就不能执行了,会被redis自动取消。
命令 | 语法 | 作用 | 返回值 | 使用场景 | 注意事项 |
|---|---|---|---|---|---|
MULTI |
| 标记事务开始,开启一个事务块 | 始终返回 | 开始一组原子性操作 | 只是设置标志,不阻塞其他客户端 |
EXEC |
| 执行事务中的所有命令 | 返回数组,包含每个命令的执行结果 | 提交事务,执行队列中的命令 | 执行期间会阻塞其他客户端命令 |
DISCARD |
| 取消事务,清空事务队列 | 始终返回 | 放弃当前事务中的所有命令 | 不会执行任何已入队的命令 |
WATCH |
| 监视一个或多个key,乐观锁机制 | 始终返回 | 在事务执行前检测key是否被修改 | 如果WATCH的key被修改,EXEC会失败 |
UNWATCH |
| 取消所有WATCH命令对key的监视 | 始终返回 | 取消之前的监视,释放资源 | 通常在事务执行后自动调用 |
所以要保障业务的一致性,不出现“超卖”的情况单靠事务是不行的,因为事务只是批处理,还是需要使用watch进行监控的。而且watch命令只是一个redis内部的一个监控列表,记录了那个会话监控了那个键,不影响其他会话对这个被监控键的修改和事务,如果被修改了那么这个watch监控的键就会被标记为脏数据,而且也只对开启这个watch的会话有效。
而且watch只作用在当前会话的当前事务,也就是说当前会话如果不开启事务,修改被监控的值也不会出现问题,就更别说其他会话的修改和事务的执行、取消了。
总结就是watch是multi的搭档,他们是一起工作的好朋友。
另外,一旦执行了exec后,之前watch监控就回被清除,还有就是断开链接后,也会关闭清除当前监控或者未执行的事务哦。所以才说事务和监控是当前会话的。
总结
本篇主要说的就是redis事务和watch之间的关系,以及redis事务和mysql事务的区别。
