Redis 事务其实没你想的强?深扒它的三个致命短板

51 阅读7分钟



大家好,我是 31 岁爱讲故事的小米,一个喜欢在通勤路上刷源码、在深夜里喝咖啡写技术文章的大哥哥。

前两天去面一家公司,本来以为是常规“扯扯项目、问问框架、聊聊八股”那种,却没想到面试官直接用一句:

“小米,聊聊事务吧。ACID 是什么?说具体点。”

我当场一个激灵:这不是八股中的八股吗?但面试嘛,不能只说术语,要讲人话,还要讲故事。

于是我端起喝了一半的美式,就像讲武侠一样,把“事务”讲成一出江湖大戏。

这一讲,面试官听得直点头,最后还说一句:“有意思,你这个解释方式我第一次听。”

所以今天,就把那套讲故事的方式写下来,给你做武功秘籍,不仅让你理解,还能让你在下一次面试时吊打法器。

第一章:ACID 是什么?江湖规矩四大门派

我当时对面试官笑笑说:

“事务嘛?就像江湖中的‘四大门派’,想维护世界和平就必须守住四个规矩。”

这四个规矩就是:

  • A - Atomicity 原子性
  • C - Consistency 一致性
  • I - Isolation 隔离性
  • D - Durability 持久性

为了让他听懂,我开始讲故事:

原子性:一刀到底,不分你我

原子性像是一个江湖杀手的规矩:

要么成功一刀毙命,要么收刀走人,不能砍一半卡住。

事务也是一样:

  • 要么全部执行成功
  • 要么全部不执行

比如银行转账:

  • A 扣 100 元
  • B 加 100 元

这两件事必须成对出现。如果只扣不加?社会乱套了。

因此数据库保证在任何异常情况下都能“自动回滚”,确保事务像一刀到底的武功。

一致性:江湖必须保持平衡

一致性像江湖的天道平衡:

不管发生什么,一定要保持前后逻辑一致。

比如银行转账前:

  • 总金额 = 1 万

转账后:

  • 总金额仍旧 = 1 万

不会凭空多钱,也不会凭空少钱。

数据库通过约束、触发器、外键、校验等保证这个平衡。

隔离性:各练各的功,不互相干扰

我当场举例:

假设你去银行取钱,同时我也去银行取钱。你在窗口操作,我在 App 上操作,我们两个的事务不能互相干扰,否则肯定会出现:

  • 你看到的余额被我修改
  • 我看到的余额被你覆盖

隔离性就是:

让每个事务拥有独立练功房间,不被其他事务干扰。

数据库有 4 个隔离级别:

  • Read Uncommitted
  • Read Committed
  • Repeatable Read
  • Serializable

隔离性越高,越安全,但性能越低。这就是江湖中典型的“安全 vs 性能”两难。

持久性:功练成了,谁也抹不掉

持久性就像练成了绝世神功,无论外界怎么风雨飘摇,你的功力不会掉。

数据库里,事务只要提交成功:

  • 数据必须持久化
  • 即使断电、宕机、服务器崩溃也不能丢

这就是 WAL 日志、redo log、binlog、checkpoint 等的价值。

到这里,面试官笑着点点头:“不错,有画面感。”

第二章:Redis 事务?呵呵,它不姓 ACID,它姓 KISS(简单粗暴)

Redis 为什么火?就是因为它足够 简单 + 暴力 + 快。于是它在事务这件事上,也延续了“简单到离谱”的路线。

我继续讲故事:

“Redis 的事务不像数据库那样是‘江湖大师’,更像街边撸串小店:

要不要一次性把菜都烤完?行。

要不要帮你回滚?不行。

要不要隔离?嗯……我们这里没那玩意。”

面试官:“继续说。”

于是我说:

第三章:Redis 支持隔离性吗?不支持!

Redis 用的是单线程模型。

也就是说,一个事务在执行的时候:

Redis 没法让其他命令插队,但也没法提供真正的隔离级别。

细说:

  • Redis 的事务只是把命令收集起来
  • 等到 EXEC 时,一次性按顺序执行
  • 但整个过程中,Redis 不会隔离读写,不会快照,不会锁住数据

所以 Redis 没有隔离性!

你甚至在事务执行前:

  • 看到的数据已经被别人改了
  • watch 的键被修改后,整个事务直接失败

典型的“你别动,我做完再说”的模式。

第四章:Redis 事务保证原子性吗?不保证!

这句我一说完,面试官立刻坐直。

因为很多人以为 Redis 的事务很强,其实不是。Redis 的事务是这样:

  • Redis 只保证 EXEC 中的命令会连续执行,不会被其他客户端插队
  • 但不保证全部成功,要失败就会局部失败

比如你执行:

结果:

  • SET a 成功
  • INCR b 报错
  • SET c 成功

Redis 不会帮你回滚!不会!不会!不会!

这就是 Redis 事务最大的坑:它不具备原子性,只具备“顺序性”。

第五章:Redis 支持回滚吗?不支持!

我给面试官举例:

“数据库像是安全顾问,做错事会帮你撤销。

Redis 像是你那粗线条的好兄弟:

哥们我帮你做了,但做错我也没办法。”

Redis 的哲学:

  • 错误命令不会导致整个事务失败
  • 成功的命令不会被撤销
  • Redis 从来不保证回滚

为什么?因为 Redis 追求速度,回滚会拖慢性能。

第六章:那 Redis 事务有什么用?

这也是很多面试官爱问的关键点。

Redis 事务适合:

  • 批量执行命令
  • 给多个命令增加“一起执行”的语义
  • 配合 WATCH 实现轻量级乐观锁
  • 保证不会被插队(单线程)

但绝不适合:

  • 强一致
  • 强隔离
  • 强原子性
  • 金融系统、转账系统

第七章:Redis 事务的三种核心机制

面试官问:“那 Redis 事务到底有哪些实现方式?”

我直接给他画三条线:

1、MULTI / EXEC / DISCARD —— Redis 原生事务机制

流程:

  1. WATCH key(可选)——监听变化,相当于乐观锁
  2. MULTI —— 开启事务
  3. 一堆命令排队
  4. EXEC —— 执行
  5. DISCARD —— 放弃事务

缺点:

  • 没有回滚
  • 没有隔离
  • 执行出错不会撤销之前命令

2、WATCH —— 乐观锁

WATCH 就是:

“我盯着某几个键,你要是敢改,我就直接放弃执行。”

适合解决:

  • 抢购
  • 库存扣减
  • CAS 更新(Compare And Set)

但 WATCH 失败后要自己重试。Redis 不会帮你。

3、Lua 脚本 —— 真正的“Redis 原子性之王”

我当时看着面试官说:

“如果你真的想在 Redis 中实现原子性,那你一定要用 Lua。”

因为:

  • Lua 脚本在 Redis 里是原子执行
  • 期间不会有其他命令插队
  • 执行要么全部成功,要么全部失败(逻辑自己写)
  • 可以用逻辑手动模拟回滚

Lua 才是 Redis 中真正的“事务神器”。

面试官直接点头:“对,这点很重要。”

第八章:总结——你在面试中应该这样说

为了让你面试不翻车,这里给你一段 最佳答案:

ACID:

  • 原子性:事务要么全部成功,要么全部失败
  • 一致性:数据前后符合规则
  • 隔离性:多个事务互相隔离
  • 持久性:提交后的数据不能丢失

Redis 事务:

  • Redis 不支持严格意义上的隔离性
  • Redis 事务不保证原子性,执行中有命令失败不会回滚
  • Redis 事务不支持回滚
  • Redis 事务机制主要包括:
    • MULTI / EXEC
    • WATCH(乐观锁)
    • Lua 脚本(唯一能保证原子性)

END

这就是我那天在面试现场讲的一套故事型解释方式。

面试官很满意,因为:

  • 不只是背概念
  • 而是真正解释了背后逻辑
  • 还能用故事让面试官不困
  • 最重要——你讲得和别人不一样

希望你在下一次面试时,也能把事务讲得像江湖一样精彩。

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!