☰
关于 Redis 锁与 MySQL 锁的苏格拉底式对话
2026/11/4 11:52:37 网站建设 项目流程

—— 关于 Redis 锁与 MySQL 锁的苏格拉底式对话

人物:

  • 苏格拉底(Socrates):探寻本质的提问者与架构哲人。
  • 年轻工程师(Theaetetus):崇尚现代技术、追求高并发性能的后端开发者。

第一幕:速度的幻觉

年轻工程师:苏格拉底,我最近陷入了深深的困惑。在我们这个时代,Redis 运行在内存之中,单机便可吞吐十万请求,快如闪电;而 MySQL 还要寻道、刷盘、维护繁重的事务日志。在分布式系统里,我们为什么不把所有的锁都换成轻盈的 Redis 分布式锁,彻底告别沉重的 MySQL 呢?

苏格拉底:告诉我,泰阿泰德(Theaetetus),当你给自家门庭安上一把精巧的锁时,你追求的究竟是“锁芯转动的速度”,还是“门后金银的周全”?

年轻工程师:自然是门后金银的周全。如果金银失窃,转得再快的锁又与废铁何异?

苏格拉底:那么,在你的系统里,什么才是真正的“金银”?是那几行执行代码的速度,还是账本上毫厘不差的账户余额?

年轻工程师:是账户余额。若余额算错,系统即便拥有百万 QPS,也不过是更快地走向破产罢了。

苏格拉底:既然如此,让我们来看看你那把“快如闪电”的 Redis 锁。它是凭借什么向你保证,门后的金银绝对安全呢?


第二幕:时间的背叛

年轻工程师:它依靠时间的契约。当我们向 Redis 申请一把锁时,会通过SET NX EX刻下一段生存时间(TTL),比如三十秒。要么我们在三十秒内完成操作后用 Lua 脚本将锁焚毁归还;要么时间一到,Redis 会自动抹去它,避免系统死锁。这难道不够完美吗?

苏格拉底:听起来十分精巧。但请设想这样一个清晨:你的代码刚刚从 Redis 手中接过这把锁,正准备更改账户,却突然遭遇了 JVM 严酷的垃圾回收停顿(Full GC STW),亦或是外部网络陷入了长久的静默。这一停顿,恰恰耗去了三十一秒。告诉我,在第三十一秒的瞬间,Redis 里的锁还在吗?

年轻工程师:……不在了。三十秒一过,Redis 已经依约将它无声地抹去了。

苏格拉底:那么,刚刚苏醒过来的那个线程,它知道自己的锁已经被收回了吗?它会停下手中即将落盘的写操作吗?

年轻工程师(冷汗):它……它感知不到!Redis 不会跨越网络向正在运行的 Java 线程投掷一道闪电让它停下。线程对自己赤身裸体一无所知,它会继续执行扣款!

苏格拉底:而就在此刻,另一个等待多时的线程走上前去,看见 Redis 门扉虚掩,于是也拿到了一把崭新的锁,踏入了同一个房间。此时,门后会发生什么?

年轻工程师:两个线程同时在修改同一个账户!原本用来保证绝对互斥的分布式锁,在这一刻形同虚设!

苏格拉底:所以你看,泰阿泰德,依赖“物理时间”作为安全界限的锁,终究会被时间的波动所背叛。当锁在无声中消亡,持有者却沦为失明之人。


第三幕:血缘与契约

年轻工程师:我明白了。但如果代码执行到一半抛出了异常——比如唯一键冲突、算术溢出、或者数据库断开连接——这时候 Redis 锁又会如何对待那些已经写到一半的数据呢?

苏格拉底:它懂得什么是“回滚(Rollback)”吗?

年轻工程师:不懂。它只是一方孤立的内存缓存,根本不知道数据库里发生了什么灾难。

苏格拉底:那 MySQL 自己的行锁(SELECT ... FOR UPDATE)呢?它的生命,又是与何物绑定在一起的?

年轻工程师:它的生命与**数据库事务(Transaction)**休戚与共。只要事务尚未COMMIT,锁便如磐石般钉在数据行上;一旦抛出异常触发ROLLBACK,不仅锁会瞬间消解,连同此前写入的一切半吊子数据,都会被 Undo Log 彻底抹平,不留一丝痕迹。

苏格拉底:一个是与数据毫无血缘、随时可能因超时而背信弃义的“外部信号”;另一个是与数据骨肉相连、同生共死的“物理枷锁”。在守护真金白银的最终一刻,你敢将身家性命全盘托付给谁?

年轻工程师:我必须信任 MySQL。唯有它能让“锁”与“数据状态”在同一片土地上同生共死。


第四幕:连接池的悬崖

年轻工程师:既然如此,苏格拉底,我们干脆把所有的并发控制都统统交给 MySQL 吧!让所有的请求都去争抢FOR UPDATE行锁,岂不是万无一失?

苏格拉底:莫急,年轻人。告诉我,在你们的国库(数据库连接池)里,一共常备着多少名精壮的卫士(Connections)?

年轻工程师:通常不过数十,至多二三百人。

苏格拉底:现在,假定一位富商要提取资产。你的卫士握住账本(拿到了 MySQL 行锁),但按照规矩,他必须先派遣一只信鸽飞往大洋彼岸的远邦交易所(Feign RPC 调用),核实这笔交易的合法性。信鸽在海上逆风飞行,整整耗去了十个呼吸的时间。在这十个呼吸里,这位手握账本的卫士在做什么?

年轻工程师:他必须霸占着这根珍贵的数据库连接,什么也不能干,只能在原地苦苦等待!

苏格拉底:若在此时,城中有一百位百姓同时涌来要求验核账目呢?

年轻工程师(惊呼):国库的卫士会被瞬间抽调一空!所有的数据库连接在几秒内耗尽!后来的百姓连最普通的查询都无法进行,整座城邦的交易系统陷入了彻底的雪崩!

苏格拉底:那么,是谁杀死了这座城邦?是并发太高,还是有人在“不可脱身的重地”里,做了“耗时漫长之事”?

年轻工程师:是我!我把需要跨洋通信的网络调用,关进了 MySQL 锁的深闺之中!在 DB 锁内做 RPC,无异于引火自焚!


第五幕:双重卫士的城邦

年轻工程师:老师,我彻底迷茫了。Redis 锁快,却会在超时中失明;MySQL 锁稳,却经不起哪怕一丝一毫的等待与网络延迟。难道在这世上,竟没有两全之策吗?

苏格拉底:泰阿泰德,一个睿智的城邦,是如何防范盗贼与维护安宁的?是将所有的守卫都堆在国库的金库门前,还是在十里之外的城门要塞便设立哨卡?

年轻工程师:自然是在城门设立哨兵盘查可疑之徒;而在金库门前,另设重锁。

苏格拉底:善哉!现在,把这道城门与这座金库,投射到你的代码之中,它们分别是谁?

年轻工程师(眼中闪过灵光):

  • 城门上的哨兵,正是 Redis 分布式锁!它驻扎在业务的最外层,以十万 QPS 的速度瞬间击退狂乱连击的重复请求。就算它需要守护长达数秒的跨洋网络调用(RPC),它也只会消耗几字节轻巧的内存,绝不动用国库哪怕一滴水源(DB 连接)!只要别人抢不到令箭,立刻打发他离开(Fail-Fast),绝不阻塞城邦道路!
  • 而金库门前的大锁,正是 MySQL 的事务行锁与状态机!只有那些穿过了城门、拿齐了外部文书、准备真正动账的请求,才获准跨入内室。在短短两毫秒之内,它落锁、记账、更新状态、释放,一气呵成!

苏格拉底:那如果……城门上的哨兵打了个盹(Redis 锁超时穿透),导致两个盗贼在同一刻溜进了内室呢?

年轻工程师:内室的卫士依然威严!即便两人同时潜入,MySQL 的行锁依然会强迫他们排队;即便排在后面的人侥幸拿到了锁,我那冰冷的状态机条件WHERE status = 'PROCESSING'也会让他扑个空(affected rows = 0),绝不给非法的二次动账留下一丝空隙!

苏格拉底:妙极了。此时此刻,你还认为 Redis 锁与 MySQL 锁,是非此即彼的仇敌吗?

年轻工程师:不,它们从来不是对手。

  • Redis 锁是哨兵,守在外层,以“闪电之速”阻挡重入,护卫流程;
  • MySQL 锁是界石,立于核心,以“磐石之重”锚定数据,捍卫底线。
  • 知其轻重,内外相济,方为架构之道。

苏格拉底:去吧,泰阿泰德。去把这些刻进你的代码里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询