
一、MySQL 事务四个核心特性原子性事务中的操作要么全部成功要么全部失败不存在部分完成的情况。一致性事务执行前后数据库的完整性约束没有被破坏。持久性事务一旦提交其结果就是永久性的不会因为系统故障等原因而丢失。这里的持久性和数据持久化存储的概念相关数据存储在硬盘上通常被认为是持久的存储在内存中则相对不持久因为重启进程或主机后数据可能丢失。隔离性多个事务并发执行时一个事务的执行不能被其他事务干扰。举一个栗子~1. 原子性Atomicity例子银行转账操作A向B转账100元需执行两步A账户减100元B账户加100元原子性体现若第二步失败如B账户异常第一步也会被回滚A账户不会扣款。事务要么全部成功A减B加要么全部失败余额不变。2. 一致性Consistency例子账户余额约束约束银行账户余额不能为负数。一致性体现若A账户仅有50元却要转出100元事务会因违反约束而失败数据库状态保持不变A仍为50元确保数据逻辑一致。3. 持久性Durability例子转账成功后的系统崩溃操作转账事务已提交A减100B加100。持久性体现即使提交后系统立即崩溃重启后数据仍保持转账后的状态A和B的余额已更新不会回滚到旧值。4. 隔离性Isolation例子并发转账的脏读问题场景事务1A向B转100元A减100未提交事务2读取A的余额若允许脏读事务2会看到A的临时中间状态如已减100但未提交隔离性体现通过隔离级别如READ COMMITTED事务2只能读到已提交的数据A的原始余额避免中间状态干扰。二.*Redis实现持久性2.1Redis作为内存数据库的特点Redis 相比 MySQL 等关系型数据库最明显的优势是效率高、速度快。优点:主要数据存储在内存中 访问效率高/速度快缺点:内存数据不持久 进程/主机重启后数据会丢失Redis 是内存数据库数据存储在内存中内存数据不持久所以需要将数据存储到硬盘以实现持久化。为了兼顾速度和持久化Redis 采用内存和硬盘都存储数据的策略。插入新数据时同时写入内存和硬盘查询数据时直接从内存读取硬盘数据主要用于 Redis 重启时恢复内存数据。这种方式虽然消耗更多空间但硬盘成本较低整体开销可接受。2.2Redis两种持久化策略1.RDBRedis DataBase概念RDB 是 Redis 的一种持久化方式定期将 Redis 内存中的所有数据写入硬盘生成一个 “快照” 文件。Redis 重启时可根据该快照恢复内存数据。举个栗子~就像警察勘察犯罪现场时会拍照记录然后根据拍照的记录来还原案发现场RDB快照文件就像警察勘察犯罪现场时的拍照记录里边存放的是二进制数据如果redis服务器重启了内存上的数据自然是没有了但是可以根据RDB快照文件”还原“数据就又可以读取上一次保存的数据了触发方式手动触发执行bgsave命令Redis 创建子进程进行快照生成不影响其他客户端请求。数据量小时生成速度快数据量大时可能需要一定时间。若不手动执行bgsave正常关闭 Redis如shutdown命令时会自动生成 RDB 快照异常关闭如kill -9或服务器掉电则可能导致内存中未保存到快照的数据丢失。自动触发在 Redis 配置文件中设置触发条件如每隔一段时间或数据发生一定次数修改时触发。文件特点RDB 文件是二进制压缩文件存放在 Redis 工作目录可在配置文件设置默认文件名为dump.rdb。生成新快照时会先写入临时文件完成后替换旧的 RDB 文件始终只有一个 RDB 文件。注意事项生成 RDB 快照成本较高不能太频繁否则可能影响性能。这会导致快照数据与实时数据存在偏差。如果 RDB 文件损坏Redis 可能无法启动或加载数据出错Redis 提供了redis-check-rdb工具用于检查 RDB 文件格式。2.AOFAppend Only File概念AOF 是另一种持久化方式与 RDB 的对比AOF 可以实现实时备份数据完整性更高但文件体积通常比 RDB 大。以日志形式记录每个写操作Redis 重启时通过重放这些操作来恢复数据。举一个栗子~类似vscode编译器上敲代码对代码有修改就马上保存而不需要有些编译器需要“ctrls”进行保存以电脑硬盘存储 “学习资料” 为例硬盘是较易损坏的部件为防止资料丢失可使用移动硬盘定期或实时备份类比 RDB 的定期备份和 AOF 的实时备份。2.2.1RDBRedis生成的RDB文件默认存放在配置文件中指定的工作目录下比如/var/lib/redis目录中。这个dump.rdb文件是RDB机制生成的二进制镜像文件采用压缩格式存储内存数据虽然节省了存储空间但会消耗一定CPU资源。Redis服务器默认开启RDB持久化功能这个二进制文件可以用vim查看但绝对不能随意修改因为一旦破坏文件格式就会导致Redis重启时加载数据失败。Redis提供了专门的rdb文件检查工具redis-check-rdb。虽然我们不会主动修改rdb文件但仍可能因网络传输等操作意外损坏文件导致Redis服务器无法启动。所以每次执行RDB持久化时Redis会先将快照数据写入临时文件生成完毕后再删除旧rdb文件并将临时文件重命名为dump.rdb确保始终只有一个rdb文件存在。bgsave命令可在.conf配置文件中查看的rdb镜像文件的位置然后再vim打开看看二进制数据为了演示我flushall了插入了几条数据但是文件感觉没啥变化这就对了因为触发保存需要手动触发和自动触发手动触发save命令和bgsave一般都用bgsavesave命令可能引起阻塞冷门关闭服务器后在重新启动服务器内存上的数据会丢失但可以在rdb文件获取持久化的数据自动触发配置文件2.2.2AOFRDB 最大的问题不能实时的持久化保存数据。在两次生成快照之间实时的数据可能会随着重启而丢失2.2.2.1AOFappend only file概念类似于 mysql 的 binlog会把用户的每个操作都记录到文件中当 redis 重新启动的时候就会读取这个 aof 文件中的内容用来恢复数据。aof 默认一般是关闭状态修改配置文件来开启 aof 功能。当开启 aof 的时候rdb 就不生效了启动的时候不再读取 rdb 文件内容了。2.2.2.2AOF机制是否有影响redis处理请求速度Q:Redis 虽然是一个单线程的服务器但是速度很快Q之所以速度快很重要的原因只是操作内存但是引入 AOF 之后又要写内存又要写硬盘。还能和之前一样快了嘛A:实际上是没有影响的并没有影响到 redis 处理请求的速度AOF 机制并非是直接让工作线程把数据写入硬盘而是先写入一个内存中的缓冲区积累一波之后再统一写入硬盘硬盘上读写数据顺序读写的速度是比较快的 (还是比内存要慢很多)随机访问则速度是比较慢的AOF 是每次把新的操作写入到原有文件的末尾。属顺序写入假设 100 个请求100 个请求的数据一次写入硬盘比分 100 次每次写入一个请求要快很多写硬盘的时候写入硬盘数据的多少对于性能影响没有很大但是写入硬盘的次数则影响很大了2.2.2.3AOF的刷新策略Q:如果把数据写入到缓冲区里本质还是在内存中呀万一这个时候突然进程挂了或者主机掉电了咋办是不是缓冲区中的数据就丢了A:是的缓冲区中没来得及写入硬盘的数据是会丢的刷新策略概念AOF 机制先把操作命令写入内存中的缓冲区而不是直接写入硬盘。如果不进行刷新这些数据就一直停留在内存缓冲区。一旦进程挂掉、主机掉电 等意外情况发生缓冲区中未写入硬盘的数据就会丢失。通过刷新能将缓冲区里的数据写入硬盘保存确保 Redis 重启时可以依据 AOF 文件恢复数据实现数据持久化所以redis 给出了一些选项让程序猿根据实际情况来决定怎么取舍制定缓冲区的刷新策略就刷新频率越高性能影响就越大同时数据的可靠性就越高.刷新频率越低性能影响就越小数据的可靠性就越低可配置值说明always命令写入 aof_buf 后调用 fsync 同步完成后返回everysec命令写入 aof_buf 后只执行 write 操作不进行 fsync每秒由同步线程进行 fsync.second命令写入 aof_buf 后只执行 write 操作由 OS 控制 fsync 频率.no命令写入 aof_buf 后只执行 write 操作由 OS 控制 fsync 频率.频率最高的数据可靠性最高性能最低.频率低一些数据可靠性也会降低性能会提高频率最低数据可靠性也是最低的性能是最高的2.2.2.AOF重写机制AOF 文件持续增长体积越来越大会影响到redis 下次启动的启动时间。因为redis 启动的时候要读取 aof 文件的内容记录了中间的过程但是实际上 redis 在重新启动的时候只是关注最终结果。注意上述 aof 中的文件有一些内容是冗余的举个栗子~有一个客户端对 redis 进行操作比如进行下列操作:lpush key 111lpush key 222lpush key 333合并为lpush key 111 222 333再比如进行下列操作set key 111set key 222set key 333合并为set key 333set key 111del keyset key 222del key合并为啥都不做就可以了。因此 redis 就存在一个机制能够针对 aof 文件进行整理操作这个整理就是能够剔除其中的冗余操作并且合并一些操作达到给 aof 文件 瘦身 这样的效果。下面拆分可能有些复杂但是无非就几步先考虑清楚1. 为啥要 “重写 AOF 文件”之前说过AOF 会把操作命令一个个记下来时间久了文件会越来越大比如存了很多冗余操作。重启 Redis 时要读这么大的文件会很慢。所以 Redis 搞了个 “重写” 机制帮 AOF 文件 “瘦身”—— 只保留能恢复最终数据的必要命令。2. 重写的核心思路用 “当前内存数据” 直接生成新 AOFRedis 启动重写时会创建一个子进程fork专门干这件事。子进程不管旧 AOF 文件里写了啥只看 “当前内存里的最终数据”比如内存里 key 对应的值是啥直接拿这个结果生成命令。子进程把这些数据用 AOF 格式文本形式的命令写到新的 AOF 文件里。这一步很像 RDB 生成快照只不过 RDB 是二进制AOF 是文本命令最终目的都是 “把内存数据存成文件”。3. 重写时父进程在干啥父进程不会闲着它继续干本职工作接收客户端新请求。新请求产生的 AOF 数据会先写到临时缓冲区aof_rewrite_buf里同时也会刷到旧的 AOF 文件里保证旧文件不会丢数据。4. 子进程和父进程的 “时间差” 问题子进程刚创建fork的瞬间会复制父进程的内存状态相当于 “定格” 了一份当时的内存数据。但 fork 之后父进程可能又处理了新请求、改了内存数据。这些 “新变化”子进程一开始是不知道的因为它复制的是 fork 时的内存。5. 怎么把 “新请求的数据” 合并到新 AOF为了不丢数据Redis 做了两步父进程把 fork 后的新请求先存到aof_rewrite_buf 缓冲区里。子进程写完 “初始内存数据” 的新 AOF 后会发信号告诉父进程。父进程收到信号就把 aof_rewrite_buf 里的 “新请求数据” 也写到新 AOF 文件里。6. 最终替换新文件换旧文件等父进程把缓冲区数据也写完新 AOF 文件就完整了包含 fork 时的内存数据 fork 后的新请求数据。这时Redis 用新 AOF 文件替换旧文件重写完成总结步骤创建子进程Redis 启动 AOF 重写时创建子进程fork父进程继续接收客户端请求子进程专门处理 AOF 文件重写。子进程重写逻辑子进程不关注旧 AOF 文件内容仅依据 fork 瞬间父进程的内存最终数据状态以 AOF 文本格式将内存数据写入新 AOF 文件过程类似 RDB 生成快照RDB 为二进制AOF 是文本命令 目标是记录当前内存数据。父进程并行操作父进程接收新请求新请求产生的 AOF 数据一方面写入临时缓冲区aof_rewrite_buf 另一方面刷入旧 AOF 文件保证旧文件数据不丢失。处理内存状态差fork 瞬间子进程复制父进程内存状态fork 后父进程新请求修改内存的数据子进程初始不知这些新数据暂存于 aof_rewrite_buf 。合并新数据与替换文件子进程写完新 AOF 文件后发信号给父进程父进程将 aof_rewrite_buf 中数据写入新文件之后用新 AOF 文件替换旧文件完成重写保障数据不丢、服务持续。Q:如果执行 bgrewriteaof 时Redis 已经在进行 AOF 重写了会怎样A:不会重复执行直接返回。避免同时重写造成资源混乱。Q:如果执行 bgrewriteaof 时Redis 正在生成 RDB 文件快照比如执行 bgsave 会怎样A:AOF 重写会等待等 RDB 快照生成完毕后再执行 AOF 重写。因为 RDB 和 AOF 重写都依赖 fork 子进程要避免冲突。Q:父进程 fork 子进程写新 AOF 后子进程很快写完新文件要替换旧文件。但父进程还在写旧 AOF —— 这有意义吗A:必须写必须考虑极端情况重写过程中服务器突然挂掉子进程内存数据丢失新 AOF 可能不完整。*AOF 与 RDB 的设计理念差异:RDB理念是 “定期备份” fork 后对新数据变化 “置之不理”只存 fork 时的内存快照 。优点是备份快、文件小缺点是可能丢 “定期备份间隔内” 的数据。AOF理念是 “实时备份”近似 fork 后用 aof_rewrite_buf 缓冲区记录新数据。优点是数据更全缺点是文件可能更大、重写更耗资源。2.2.2AOF混合持久机制打开AOF文件查看内容发现怎么跟RDB文件一样写入的是二进制而不是文本AOF 纯文本方式的问题AOF 本来是按照文本的方式来写入文件的. 但是文本的方式写文件, 后续加载的成本是比较高的解释Redis 的 AOFAppend - Only File只追加文件持久化最初设计是把每个写操作以文本命令的形式一行行追加到 AOF 文件里 。比如执行SET key value就把这条命令文本存到文件 。但文本形式有缺点Redis 重启加载 AOF 文件时得逐行解析这些文本命令再执行数据量大时解析、执行这些命令就会很慢加载成本时间、性能方面高 。混合持久化的引入及配置配置文件可以修改混合持久配置redis 就引入了 混合持久化 的方式结合了 rdb 和 aof 的特点。 aof-use-rdb-preamble yes 这个选项为 yes 表示开启混合持久化 (修改配置项之后要记得重新启动服务器)解释为解决纯 AOF 加载慢的问题Redis 搞了 “混合持久化” 。它结合了 RDBRedis 数据快照把内存数据按二进制格式快照保存 加载快 和 AOF 的优点 。配置项aof-use-rdb-preamble设为yes就是开启混合持久化 设为no就是关闭 。而且改了这个配置后必须重启 Redis 服务器配置才能生效 。混合持久化的工作流程按照 aof 的方式, 每一个请求/操作, 都记录入文件. 在触发 aof 重写之后, 就会把当前内存的状态按照 rdb 的二进制格式写入到新的 aof 文件中. 后续再进行的操作, 仍然是按照 aof 文本的方式追加到文件后面~~解释正常情况下混合持久化开启后Redis 还是先按纯 AOF 那套每个写操作像SETDEL等 都以文本命令形式追加到 AOF 文件保证数据实时性 。当触发 AOF 重写比如达到配置的文件大小增长率、达到指定文件大小等条件 Redis 会先把当前内存里的所有数据用 RDB 的二进制快照方式写入到新的 AOF 文件开头 。这一步好处是RDB 二进制格式加载快相当于用 RDB 快速 “定格” 一份当前完整数据快照 。接着后续新产生的写操作又回到 AOF 文本追加的老样子继续往文件后面加文本命令 。这样结合后加载 AOF 文件时先快速加载开头的 RDB 二进制部分恢复大部分数据再接着解析后面的 AOF 文本命令既保证了数据完整性像 AOF 那样记录每一步操作 又提升了加载速度借助 RDB 二进制的高效 。添加新的数据后续操作使用文本格式进行进行追加保存了三.*Redis实现事务3.1Redis 事务核心作用Redis 事务核心作用——“打包” 防插队Redis 事务最主要意义是 **“打包” 命令 **把多个操作塞一块执行避免其他客户端命令 “插队” 插中间。举个栗子~你和女朋友约好去吃烧烤你先到烧烤店点了羊肉串、五花肉相当于 “开启事务把这些点单操作放进队列” 还跟服务员说 “先别烤等人齐了再烤” 女朋友到了又加烤韭菜、腰子继续往事务队列里加操作 最后你喊 “开始烤吧”执行事务 这时候服务员就会把你们点的所有串一起烤中间不会被其他桌点单插队 。对应 Redis 里的逻辑每个客户端都有自己的事务队列前提是开启了事务开启事务MULTI 后发的命令先存队列里不立即执行 等执行事务EXEC 命令队列里的操作才会按顺序在 Redis 主线程里依次执行执行完再处理其他客户端请求 。3.2Redis 事务 vs MySQL 事务Redis 事务 vs MySQL 事务 —— 为啥 “简单”MySQL 事务很强大有原子性、一致性、持久性、隔离性 但代价高要存更多数据占空间执行起来也更耗时 。Redis 为了高效、轻量化没搞这么复杂主要靠 “打包执行” 解决问题所以功能上和 MySQL 事务有差距 。不过 Redis 也能一定程度应对 “超卖” 这类场景比如秒杀、库存扣减 场景举例放 5000 台货要是 5001 个人下单成功就是超卖 。普通写法不加控制会有问题 先查库存库存0 就下单、扣库存。但多线程 / 多客户端同时操作时可能都查到库存够最后超卖 。Redis 事务解决思路把 “查库存 下单 扣库存” 打包成事务保证这些操作连续执行中间不被插队避免超卖 。以前多线程里靠加锁Redis 里用事务就可以 3.3Redis 事务的操作命令核心命令举个栗子~MULTI开启事务之后发的命令进队列 。EXEC执行事务把队列里的命令按顺序执行 。DISCARD放弃事务清空队列里的命令不执行 。WATCH监控键事务执行前要是监控的键被其他客户端修改了事务里的命令就不会执行类似 “乐观锁” 预防数据冲突 。MULTI开启事务并输入数据但是queue并没有执行命令使用keys 找不到keyexec:执行命令DISCARD丢弃事务原有的操作watch命令实际操作示例看命令交互更清楚 开启事务后发命令SET key1 111 SET key2 222 … 这些命令会显示 QUEUED 存在服务器事务队列里 。这时候其他客户端查这些 key拿不到结果还没执行 。执行 EXEC 队列里的命令才真正执行返回结果 。要是事务执行前服务器重启效果和 DISCARD 一样事务被放弃 。WATCH 的作用比如客户端 1 WATCH key 后开启事务、改 key 值客户端 2 也改这个 key 。客户端 1 执行 EXEC 时Redis 发现 key 被改过事务里的修改就不执行返回空 避免数据冲突3.1Redis 事务的 “不足” 场景Redis 事务的 “不足” 适用场景不足没有 MySQL 事务那样严格的原子性、一致性保障 。要是事务里某个命令执行失败比如语法错 其他命令可能继续执行也没有回滚机制执行中出错不会自动回滚 。集群模式下不支持事务 。应用场景比 MySQL 事务少更像 “辅助打包执行” 的工具 。适用场景主要解决 “命令打包执行避免插队” 的问题像秒杀库存扣减、简单的操作序列连续执行等场景 。要是需要复杂的事务逻辑强原子性、回滚 还是得靠 MySQL 这类数据库 。3.3额外补充Lua 脚本和事务的关系 Redis 原生命令里没有复杂条件判断比如 IF count 0 这种 但可以用 Lua 脚本 实现类似逻辑而且 Lua 脚本也能打包批量执行算是事务的 “进阶版” 。不过日常简单场景用 Redis 事务的 MULTI/EXEC 就够啦简单说Redis 事务就是个 “命令打包器” 把一堆操作捆一起连续执行解决插队问题和 MySQL 事务功能、复杂度不同各有各的应用场景理解清楚这些用的时候就知道啥场景选 Redis 事务啥场景得靠 MySQL 啦3.4Redis 事务的特点Redis 事务的特点对比 MySQL 事务Redis 的事务比 MySQL 的事务简单很多主要体现在这几个特性上原子性MySQL 事务强调 “要么全成功要么全回滚”但 Redis 事务不支持回滚。即使事务里某条命令执行失败其他命令也可能继续执行除非语法错误等严重问题 。一致性Redis 不保证 “事务执行前和执行后数据绝对一致” 。比如执行过程中遇到错误不会像 MySQL 那样严格回滚到初始状态可能出现部分执行后的中间状态。持久性Redis 主要靠内存存数据虽然有 RDB、AOF 持久化但和 MySQL 基于磁盘事务日志的持久化逻辑不同持久性保障相对弱一些依赖持久化策略 。隔离性Redis 是单线程处理请求所有命令本质上 “串行执行” 。不像 MySQL 有多线程并发需要复杂的隔离级别读未提交、读已提交等 Redis 天然通过单线程保证 “命令执行的顺序性”隔离性简单很多。Redis 里操作事务的基础命令multi开启事务之后输入的命令会进入事务队列暂不执行 。exec执行事务把队列里的命令按顺序执行。discard放弃事务清空事务队列里的命令不执行。watch/unwatch配合事务实现 “乐观锁” 功能后面详细讲 。另外Redis 里的 Lua 脚本也能实现类似事务的效果官方甚至说 “事务能做的事Lua 脚本都能替代” 。因为 Lua 脚本可以把多个命令打包让 Redis 一次性执行中间不被其他操作打断。3.5Redis 的 “乐观锁”Redis 的 “乐观锁”watch 命令的作用Redis 用watch命令实现类似 “乐观锁” 的逻辑核心是通过版本号判定避免数据冲突。用生活例子理解1. 场景设定模拟多客户端操作同一个 key假设 key 初始版本号是 1客户端 1 和客户端 2 都要修改这个 key客户端 1watch key给 key “打标记”记录当前版本号比如 1 。multi开启事务准备操作 key。set key 222把操作放进事务队列还没执行 。exec执行事务时Redis 会检查 key 的版本号。客户端 2如果在客户端 1 执行 exec 前客户端 2 执行了 set key 333 → 这会让 key 的版本号变大比如从 1 变 2 。2. “乐观锁” 的判定逻辑当客户端 1 执行exec时Redis 会做这些事对比 “当前 key 的版本号” 和 “watch 时记录的版本号”。如果版本号一致说明没人改 key 执行事务队列里的命令比如set key 222正常修改数据。如果版本号不一致说明有其他客户端改了 key比如客户端 2 改过 放弃事务里的操作exec返回nil客户端 1 的修改不生效。简单说watch就是给exec加了个 “条件判定”只有数据没被其他客户端修改过事务才执行否则就放弃避免覆盖别人的修改。3. watch 命令的使用规则watch必须在multi之前用先标记要监控的 key再开启事务。只要对监控的 key 做了修改不管哪个客户端 key 的版本号就会变大。事务执行时Redis 会拿最新版本号和watch时记录的对比决定是否执行事务。3.6总结关于Redis 事务怎么用、有啥特点简单流程watch可选做乐观锁 →multi开启事务 → 发多个命令进队列 →exec执行 /discard放弃。核心价值把多个命令 “打包”让它们连续执行中间不被其他客户端的操作插队解决类似 “超卖”“库存冲突” 问题配合watch更稳 。和 MySQL 事务的区别Redis 事务更轻量化、简单化不追求严格的原子性、一致性适合高并发下简单的 “操作打包” 场景MySQL 事务功能强、保障严适合数据完整性要求高的业务比如转账 。理解这些你就明白 Redis 事务的设计逻辑了用简单的 “队列 执行” 模式解决 “命令连续执行” 的问题用watch实现乐观锁避免数据冲突。虽然功能不如 MySQL 事务全面但胜在轻量、高效适合 Redis 本身的使用场景