【Redis】深入解析 Redis 事务:特性、操作及其与 MySQL 事务的区别

目录


Redis 事务

什么是事务

Redis 的事务和 MySQL 的事务概念上是类似的. 都是把⼀系列操作绑定成⼀组. 让这⼀组能够批量执⾏.

回顾 MySQL 事务:

  • 原子性:把多个操作打包成一个整体了
  • 一致性:事务执行之前和之后,数据都不能离谱
  • 持久性:事务中做出的修改都会存硬盘
  • 隔离性:事务并发执行涉及到的一些问题

但是注意体会 Redis 的事务和 MySQL 事务的区别:

  • 弱化的原⼦性:redis 没有 "回滚机制",只能做到这些操作 "批量执⾏",不能做到 "⼀个失败就恢复到初始状态",也就是不保证全部执行成功,其中有失败的就失败了,不理会。
  • 不保证⼀致性:不涉及 "约束",也没有回滚。MySQL 的⼀致性体现的是运⾏事务前和运⾏后,结果都是合理有效的,不会出现中间⾮法状态。
  • 不需要隔离性:也没有隔离 级别,因为不会并发 执⾏事务(redis 单线程处理请求)。
  • 不需要持久性:是保存在内存的。是否开启持久化,是 redis-server ⾃⼰的事情,和事务⽆关。

因此,Redis 的事务的功能相⽐于 MySQL 来说,是弱化很多的、只能保证事务中的这⼏个操作是 "连续的",不会被别的客⼾端 "加塞",仅此⽽已。

就官方角度而言,也不太认为他们自己是具有原子性的,可能更加认同原子性更贴合 MySQL 的那种设定,也就是 MySQL 这种才算事务。

Redis 事务本质上是在服务器上搞了⼀个 "事务队列"。每次客⼾端在事务中进⾏⼀个操作,都会把命令先发给服务器,放到 "事务队列" 中,这是保证不被插队(但是并不会⽴即执⾏)

⽽是会在真正收到 EXEC 命令之后,才真正执⾏队列中的所有操作。此时 Redis 主线程就会把事务的所有操作都执行完,再处理其他客户端的操作

问:Redis 的事务为什么不设计得和 MySQL 一样强大呢?

答:MySQL 的事务是付出了很大的代价。空间上要花费更多的空间来存储更多的数据;时间上也要有更大的执行开销。正是因为这些问题,Redis 才有上场的机会,Redis 实现了 MySQL 的事务的话就失去了本身的优势。

Redis 事务的应用场景:相比 MySQL 的事务其实应用场景没那么多。Redis 如果是按照集群模式部署,是不支持事务的。

事务操作

MULTI

multi

开启⼀个事务. 执⾏成功返回 OK.

实例

cmd 复制代码
127.0.0.1:6379> MULTI
OK

EXEC

exec

真正执⾏事务.

实例

cmd 复制代码
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> set k2 2
QUEUED
127.0.0.1:6379> set k3 3
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) OK

每次添加⼀个操作,都会提⽰ "QUEUED",说明命令已经进⼊客⼾端的队列了。

真正执⾏ EXEC 的时候,客⼾端才会真正把上述操作发送给服务器.

此时就可以获取到上述 key 的值了.

cmd 复制代码
127.0.0.1:6379> get k1
"1"
127.0.0.1:6379> get k2
"2"
127.0.0.1:6379> get k3
"3"

DISCARD

discard

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

实例

cmd 复制代码
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 1
QUEUED
127.0.0.1:6379> set k2 2
QUEUED
127.0.0.1:6379> DISCARD
OK

127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k2
(nil)

当开启事务,并且给服务器发送若干命令之后,此时服务器重启,效果相当于 discard

WATCH

watch

在执⾏事务的时候,如果某个事务中修改的值,被别的客⼾端修改了,此时就容易出现数据不⼀致的问题。watch 必须搭配事务使用,并且是在 multi 之前使用

实例

cmd 复制代码
# 客⼾端1 先执⾏
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key 100
QUEUED

# 客⼾端2 再执⾏
127.0.0.1:6379> set key 200
OK

# 客⼾端1 最后执⾏
127.0.0.1:6379> EXEC
1) OK

此时,key 的值是多少呢??

从输⼊命令的时间看,是客⼾端1 先执⾏的 set key 100。客⼾端2 后执⾏的 set key 200

但是从实际的执⾏时间看,是客⼾端2 先执⾏的,客⼾端1 后执⾏的

cmd 复制代码
127.0.0.1:6379> get key 
"100"

这个时候,其实就容易引起歧义.

因此,即使不保证严格的隔离性,⾄少也要告诉⽤⼾,当前的操作可能存在⻛险.

watch 命令就是⽤来解决这个问题的。watch 在该客⼾端上监控⼀组具体的 key

当开启事务的时候,如果对 watch 的 key 进⾏修改,就会记录当前 key 的 "版本号". (版本号是个简单的整数,每次修改都会使版本变⼤。服务器来维护每个 key 的版本号情况)

在真正提交事务的时候,如果发现当前服务器上的 key 的版本号已经超过了事务开始时的版本号,就会让事务执⾏失败。(事务中的所有操作都不执⾏)

实例

客⼾端1 先执⾏

cmd 复制代码
127.0.0.1:6379> watch k1 # 开始监控 k1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 100 # 进⾏修改,从服务器获取 k1 的版本号是 0. 记录 k1 的版本
QUEUED
127.0.0.1:6379> set k2 1000 
QUEUED

只是⼊队列,但是不提交事务执⾏

客⼾端2 再执⾏

cmd 复制代码
127.0.0.1:6379> set k1 200 # 修改成功,使服务器端的 k1 的版本号 0 -> 1
OK

客⼾端1 再执⾏

cmd 复制代码
127.0.0.1:6379> EXEC # 真正执⾏修改操作,此时对⽐版本发现,客⼾端的 k1 的版本
(nil)

127.0.0.1:6379> get k1
"200"

127.0.0.1:6379> get k2
(nil)

此时说明事务已经被取消了. 这次提交的所有命令都没有执⾏

UNWATCH

unwatch

取消对 key 的监控.

相当于 WATCH 的逆操作. 此处不做演⽰。

WATCH 的实现原理

watch 的实现,类似于一个"乐观锁"

乐观锁,悲观锁不是指一个具体的锁,而是指某一类锁的特性

  • 乐观锁:加锁之前,预期后续锁冲突的概率比较低(乐观锁,防君子不防小人)
  • 悲观锁:加锁之前,预期后续锁冲突的概率比较高

锁冲突:两个线程针对同一个锁枷锁,一个能加锁成功,另一个就只能阻塞等待

对于锁冲突概率高或低,接下来要做的工作是不一样的,两者应对场景不一样

Java 的 synchronized 可以在悲观/乐观之间自适应,实际上是一种悲观锁的实现,因为它总是假设有并发冲突发生。当一个线程尝试获取锁而锁被占用时,该线程并不会立即进入阻塞状态。相反,它会进行几次"自旋",在某种程度上接近乐观锁的行为(尝试不阻塞线程)

Redis 的 watch 相当于是基于 版本号,来实现了"乐观锁"

当执行 watch key 时就会给这个 key 安排一个版本号,这个版本号可以理解成一个"整数",每次修改时,版本号都会"变大"。在执行事务中命令的时候就会判定这个 key 的版本号和最初 watch 的时候记录的版本号是否一致。如果一致说明没有被别的客户端修改,才会真正执行事务的操作;如果不一致说明被别的客户端修改,就会直接丢弃事务的操作,然后返回一个 nil。

这样的设定类似 CAS 的 ABA 问题

Redis 的 lua脚本,也能起到类似于事务的效果。官网上说,事务这里的任何能实现的效果,都可以用 lua脚本代替。

相关推荐
廋到被风吹走6 分钟前
【数据库】【MySQL】分库分表策略 分类、优势与短板
数据库·mysql·分类
嘻哈baby42 分钟前
Redis高可用部署与集群管理实战
数据库·redis·bootstrap
五阿哥永琪2 小时前
MySQL 慢查询定位与 SQL 性能优化实战指南
sql·mysql·性能优化
Java爱好狂.3 小时前
Java面试Redis核心知识点整理!
java·数据库·redis·分布式锁·java面试·后端开发·java八股文
阿杆4 小时前
如何在 Spring Boot 中接入 Amazon ElastiCache
java·数据库·redis
xiaok5 小时前
GROUP BY进阶用法
mysql
李慕婉学姐5 小时前
【开题答辩过程】以《基于Android的健康助手APP的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
android·java·mysql
qq_12498707535 小时前
基于springboot健康养老APP的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·mysql·微信小程序·毕业设计
亚林瓜子6 小时前
mysql命令行手动导入csv数据到指定表
数据库·mysql·gui·csv·cli·db·import
一分半心动6 小时前
lnmp架构 mysql数据库Cannot assign requested address报错解决
linux·mysql·php