Redis 事务机制之ACID属性

news/2025/2/21 19:29:45/

事务属性

事务是对数据库进行读写的一系列操作。在事务执行时提供ACID属性保证: 包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

  • 原子性(Atomicity): 事务中多个操作要么全部成功,要么全部失败。
  • 一致性(Consistency): 数据库中的数据在事务执行前后是一致的。
  • 隔离性(Isolation): 一个事务的执行不被其他事务影响。
  • 持久性(Durability):数据库执行事务后,数据的修改被持久化保存。

Redis 中的事务如何工作:

  • Redis 事务允许在一个步骤中执行一组命令,Redis提供了事务相关的命令: MULTI 、 EXEC、 DISCARD、WATCH。
  • Redis 事务做了两个重要的保证:
    1)事务中的所有命令都被序列化并按顺序执行。另一个客户端发送的请求永远不会在执行Redis事务的过程中被服务。这保证了命令作为单个隔离操作执行。
    2)EXEC命令触发事务中所有命令的执行,因此,如果客户机在调用EXEC命令之前在事务的上下文中失去与服务器的连接,则不会执行任何操作,相反,如果调用EXEC命令,则执行所有操作。

原子性(Atomicity)

Redis事务机制是否能够保证原子性? 在事务期间,可能会遇到两种命令错误:

  • 命令可能无法排队,因此在调用EXEC之前可能会出现错误。例如,该命令可能在语法上是错误的(参数数量错误,命令名称错误,…),或者使用不存在的命令,或者可能存在一些关键条件,如内存不足条件(如果服务器使用maxmemory指令配置了内存限制)。
# 开启事务
192.168.88.11:6380> multi
OK# 语法错误,redis不支持该命令
192.168.88.11:6380> sett key hello 
(error) ERR unknown command `sett`, with args beginning with: `key`, `hello`, # 正确命令,Redis命令入队
192.168.88.11:6380> incr counter 
QUEUED# 执行事务,因之前命令有错,事务无法执行
192.168.88.11:6380> exec
(error) EXECABORT Transaction discarded because of previous errors.# 键counter为空
192.168.88.11:6380> get counter
(nil)
  • 在调用EXEC之后,命令可能会失败,例如我们对具有错误值的键执行了操作(例如对字符串值调用列表操作)。即命令和操作的数据类型不匹配。但Redis实例并没有检查出错误。这种属于运行时错误,Redis在执行这些事务操作时就会报错。
    需要注意的是, 即使命令失败,队列中的所有其他命令也会被处理——Redis 不会停止命令的处理。
    需要注意的是, 即使命令失败,队列中的所有其他命令也会被处理——Redis 不会停止命令的处理。
    需要注意的是, 即使命令失败,队列中的所有其他命令也会被处理——Redis 不会停止命令的处理。
# 键 k1 为字符串类型
192.168.88.11:6380> type k1
string# k1 当前值为 hello 
192.168.88.11:6380> get k1
"hello"# 开启事务
192.168.88.11:6380> multi 
OK# 对 k1 字符串类型调用列表操作: LPUSH,此时并不报错
192.168.88.11:6380> LPUSH k1 1
QUEUED# 继续执行append操作
192.168.88.11:6380> APPEND k1 world
QUEUED# 执行事务,第一个操作报错,第二个操作正常执行
192.168.88.11:6380> exec
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) (integer) 10# k1 的傎已修改
192.168.88.11:6380> get k1
"helloworld"

Redis 不支持事务回滚,因为支持回滚会对 Redis 的简单性和性能产生重大影响。虽然 Redis 提供了 DISCARD 命令,DISCARD可用于中止事务。丢弃命令队列。 此时,不会执行任何命令,连接状态恢复正常。起不到回滚的效果。

DISCARD可用于中止事务。丢弃命令队列。不是回滚命令。
DISCARD可用于中止事务。丢弃命令队列。不是回滚命令。
DISCARD可用于中止事务。丢弃命令队列。不是回滚命令。

# 设置foo的值为1
192.168.88.11:6380> SET foo 1
OK# 开启事务
192.168.88.11:6380> MULTI
OK# 自增操作
192.168.88.11:6380> INCR foo
QUEUED# 执行DISCARD 命令,放弃事务
192.168.88.11:6380> DISCARD
OK# 再次读取foo值,值没有被修改
192.168.88.11:6380> GET foo
"1"
  • 如果在执行事务 EXEC 命令时,Redis实例或机器意外故障。导致事务执行失败。
    1)如果没有开启AOF,操作日志不会被记录,数据丢弃。
    2)如果有开启AOF,AOF 日志是仅追加日志,断电时也不会出现损坏问题。即使由于某种原因(磁盘已满或其他原因)日志以半写命令结束,redis-check-aof 工具也能够轻松修复它。

如果 AOF 被截断,该怎么办?
当 aof-load-truncated 启用时,AOF 中的最后一个命令可能会被截断。Redis 的最新主要版本无论如何都能够加载 AOF,只是丢弃文件中最后一个格式不正确的命令。在这种情况下,会有下面日志:此时事务操作不会再被执行,从而保证原子性。

2213:M 22 Feb 2024 14:40:08.204 # !!! Warning: short read while loading the AOF file !!!
2213:M 22 Feb 2024 14:40:08.204 # !!! Truncating the AOF at offset 237 !!!
2213:M 22 Feb 2024 14:40:08.204 # AOF loaded anyway because aof-load-truncated is enabled
2213:M 22 Feb 2024 14:40:08.204 * DB loaded from append only file: 0.000 seconds

当 aof-load-truncated 未启用时,Redis无法启动,会有下面的日志:

2302:M 22 Feb 2024 14:56:13.638 # Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.

需要使用 redis-check-aof 工具修复AOF 日志文件,把未完成的事务操作从 AOF 文件中删除。通过AOF 恢复实例后,事务操作不会再被执行,保证了原子性。

执行以下步骤进行恢复:

root@ubuntu-x64_01:/data/redis/data# cp appendonly.aof appendonly.aof.bak 

制作 AOF 文件的备份副本。

使用Redis附带的redis-check-aof 工具修复原始文件:

root@ubuntu-x64_01:/data/redis/data# redis-check-aof --fix appendonly.aof
0x              f5: Expected to read 6 bytes, got 0 bytes
AOF analyzed: size=245, ok_up_to=166, diff=79
This will shrink the AOF from 245 bytes, with 79 bytes, to 166 bytes
Continue? [y/N]: y
Successfully truncated AOF

所以Redis对事务原子性属性不同场景下会不同。具体如下:
1)如果命令语法错误、在命令入队就报错,事务将无法执行,保证了原子性。
2)如果命令本身正确,命令入队成功。但实际执行时报错。无法保证原子性。
3)如果EXEC命令执行时实例故障。此时有开启AOF,可以保证原子性。

综合上面场景:
当命令入队时没报错,实际执行时报错,运行时错误。不能保证原子性!!!
当命令入队时没报错,实际执行时报错,运行时错误。不能保证原子性!!!
当命令入队时没报错,实际执行时报错,运行时错误。不能保证原子性!!!

在这里插入图片描述

一致性(Consistency)

事务的一致性要看具体场景,事务在执行时可能会有错误命令、参数数量错误、实例故障等因素影响。

1) 调用EXEC之前出现错误
即命令入队时报错,例如,该命令可能在语法上是错误的(参数数量错误,命令名称错误,…),或者使用不存在的命令。这种情况下,事务本身不会被执行,可以保证一致性。

在这里插入图片描述
2)调用EXEC之后出现错误
在调用EXEC之后,命令可能会失败,例如,由于我们对具有错误值的键执行了操作(例如对字符串值调用列表操作),在这种情况下,有错误的命令不会执行,正确的命令成功执行。不影响数据的一致性。

在这里插入图片描述

  1. 执行事务 EXEC 命令实例故障
    如果在执行事务 EXEC 命令时,Redis实例或机器意外故障。导致事务执行失败。要根据是否持久化分场景分析:
    3.1)如果没有持久化(即没开启RDB和AOF),无持久化无数据。数据是一致的。
    3.2)如果有RDB持久化,事务执行时RDB快照不会执行。数据是一致的。
    3.3)如果有AOF持久化,事务可能没被记录或记录不完整(使用Redis附带的redis-check-aof 工具修复原始文件),数据是一致的。

综合上面场景:
当命令语法错误、命令执行错误 或 Redis发生意外故障场景下。Redis事务机制对一致性属性有保证的。
在这里插入图片描述

隔离性(Isolation)

事务是在EXEC命令后才能真正执行,EXEC命令之前,命令会先进入队列。 分两种场景来分析:

1)调用EXEC之前

事务在 EXEC 命令前执行,命令操作是暂存在命令队列,并没有真正执行,此时。隔离性使用 WATCH 机制来实现保证 。

WATCH用于为 Redis 事务提供检查和设置 (CAS) 行为。

监视键是为了检测针对它们的更改。如果在执行EXEC命令之前至少修改了一个被监视的键,那么整个事务将终止,EXEC将返回一个Null应答来通知事务失败。

比如,我们要对键 foo 加 10 操作:

只有当我们有一个客户端在给定时间内执行操作时,这才会可靠地工作。如果多个客户端同时尝试增加键,就会出现竞争条件。例如,客户端A和B将读取旧值,例如:1。两个客户端都将该值增加到11,并最终设置为键的值。所以最后的值是11而不是21。
在这里插入图片描述

此时,我们需要使用WATCH命令来确保检测旧值没有其它客户端修改过,如果修改了,存在竞争条件,在调用WATCH和调用EXEC之间的时间内,另一个客户端修改了val的结果,则事务将失败。否则事务就能正常执行。

我们只需要重复这个操作,希望这次不会出现新的竞争。这种形式的锁定称为乐观锁定。在许多用例中,多个客户端将访问不同的键,因此不太可能发生冲突,通常不需要重复操作。

# 客户端1 
192.168.88.11:6380> CLIENT ID
(integer) 13192.168.88.11:6380> set key "java"
OK192.168.88.11:6380> watch key 
OK
192.168.88.11:6380> multi
OK192.168.88.11:6380> append key go
QUEUED
192.168.88.11:6380> exec
(nil)192.168.88.11:6380> get key 
"javapython"# -----------------------------------------------# 客户端 2 
192.168.88.11:6380> CLIENT ID
(integer) 14# 此步骤在 客户1 开启事务后 执行 
192.168.88.11:6380> append key python
(integer) 10192.168.88.11:6380> get key 
"javapython"

在这里插入图片描述

2)调用EXEC之后

因为Redis主线程是单线程执行命令,EXEC命令执行后,Redis会先执行命令队列中的所有命令执行完。再处理其它客户请求操作命令。所以这种情况不会影响事务的隔离性。

在这里插入图片描述

综上场景,使用WATCH命令在事务执行前,检测它们的更改。如果在执行EXEC命令之前至少修改了一个被监视的键,那么整个事务将终止,EXEC将返回一个Null应答来通知事务失败,避免事务的隔离性被破坏。如果在EXEC命令之后执行,Redis会保证先把命令队列中的所有命令执行完。不会破坏事务的隔离性。

在这里插入图片描述

持久性(Durability)

事务的持久性取决于Redis持久化配置,如果Redis没有配置RDB和AOF持久化,无持久化无数据。无持久性保证。那么事务属性肯定得不到保证。如果配置了持久性,根据不同的场景分析如下:

1)如果有RDB持久化,事务执行后,RDB快照还未执行就故障。无持久性保证。
2)如果有AOF持久化,取决于三种配置(no、everysec、alway)都存在数据丢失的情况,无持久性保证。

综上,不管是哪种模式,持久性都无法保证。 Redis本身是内存数据库,持久性并不是必须的属性。

在这里插入图片描述

小结

1)Redis事务ACID属性可以保证一致性和隔离性。但是无法保证原子性、持久性(由于Redis本身是内存数据库),持久性并不是必须的属性。一般关注ACI属性。
2)Redis的原子性在使用时较复杂,请参考官方文档操作命令,要确保命令运行正确,否则原子性无法得到保证。
3)在使用事务时,可以结合 pileline 一次发出多个命令而无需等待每个命令的响应来提高性能、或使用LUA脚本。
4)事务还需要考虑其它的,比如redis中的操作是redis脚本,它是事务性的。Redis 事务做的所有事情,你也可以用脚本来做,而且通常脚本会更简单、更快。


http://www.ppmy.cn/news/1360500.html

相关文章

ClickHouse 指南(三)最佳实践 -- 稀疏主索引

在ClickHouse主索引的实用介绍 ClickHouse release 24.1, 2024-01-30 1、简介 在本指南中&#xff0c;我们将深入研究ClickHouse索引。我们将详细说明和讨论: ClickHouse中的索引与传统的关系数据库管理系统有何不同ClickHouse是如何构建和使用表的稀疏主索引的什么是在Clic…

spring boot3登录开发-3(账密登录逻辑实现)

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途。 目录 前置条件 内容简介 用户登录逻辑实现 创建交互对象 1.创建用户登录DTO 2.创建用户登录VO 创建自定义登录业务异…

petalinux_zynq7 驱动DAC以及ADC模块之六:qt显示adc波形

前文&#xff1a; petalinux_zynq7 C语言驱动DAC以及ADC模块之一&#xff1a;建立IPhttps://blog.csdn.net/qq_27158179/article/details/136234296petalinux_zynq7 C语言驱动DAC以及ADC模块之二&#xff1a;petalinuxhttps://blog.csdn.net/qq_27158179/article/details/1362…

flink类加载器原理与隔离(flink jar包冲突)

flink类加载器原理与隔离 Java 类加载器解决类冲突基本思想什么是 Classpath?Jar 包中的类什么时候被加载?哪些行为会触发类的加载?什么是双亲委派机制?如何打破双亲委派机制? Flink 类加载隔离的方案Flink是如何避免类泄露的?Flink 卸载用户代码中动态加载的类Flink 卸载…

Django——ORM增删改查

基本对象 model.objects 创建数据 可以通过django编写的命令行方式快捷创建数据 python manage.py shell 如果对模型层有任何修改都需要重启shell&#xff0c;否则操作容易出错 在shell中我们需要先引入我们的模型&#xff0c;如from bookstore.models import Book 然后通过…

java:Java中的数组详解

目录 Java数组的定义和特点&#xff1a; Java数组的初始化和赋值 Java数组的常用操作 1. 遍历数组 2. 获取数组长度 3. 访问数组元素 4. 数组的拷贝 多维数组 数组的排序和查找 冒泡排序&#xff1a; 快速排序 &#xff1a; 二分查找&#xff1a; 数组的应用&#xff1a; Java数…

Kodi设置界面语言为中文

Kodi设置界面语言为中文需要注意的一点就是&#xff0c;先要设置&#xff1a;皮肤&#xff08;Skin&#xff09;---》字体&#xff08;Fonts&#xff09;---》基于Arial字体&#xff08;Arial based&#xff09;&#xff0c;否则在设置后&#xff1a;区域&#xff08;Regional&…

18个惊艳的可视化大屏(第七辑):场馆与园区方向

本期分享智慧场馆和智慧园区方向的可视化大屏&#xff0c;各位老铁上车&#xff0c;坐稳了&#xff0c;上图啦。