Skip to content

第 8 章:八小时的停表 (The Eight-Hour Stop)

2004年3月,雷德蒙德 (Redmond) 的阴雨连绵不绝。

创世软件的股价正随着 Hello 平台的用户破亿而节节攀升。此时的西拉斯·霍恩,已经不再满足于仅仅做一个“留言板”和“照片墙”的老板。他盯上了一块能带来惊人利润的新大陆——会员体系与精准社交

“我们要把一亿个用户,分成三六九等!”

在 113 号楼战情室的例会上,西拉斯指着白板上的商业蓝图,口若悬河:“普通用户、VIP 会员、钻石会员!我们要知道他们的生日、他们的信用卡等级、他们有多少个互相关注的好友!这是我们未来变现的基础!”

他转头看向开发主管:“我需要在系统里立刻加上这些功能!最迟明天下午,我要看到 ‘VIP_Status’(VIP状态)的金色徽章挂在那些付费用户的头像旁边!”

开发主管擦了擦额头的汗,转头看向了坐在角落里的李思和首席 DBA 文斯。

“这……西拉斯,这涉及到核心数据库表结构的变更。”开发主管小心翼翼地说。

“那就变更啊!”西拉斯不耐烦地敲着桌子,“不就是加几个字段吗?你们这群拿着高薪的工程师,连在数据库里加一列都做不到吗?”

文斯推了推金丝眼镜,清了清嗓子:“西拉斯,理论上这非常简单。我们只需要在后端那张存放着一亿条记录的核心用户表 Users_Hello 上,执行一句极其基础的 SQL 语句:ALTER TABLE Users_Hello ADD VIP_Status INT;。”

“那就去执行!”

“但是,”李思冷冷的声音打断了西拉斯的催促,“这句‘极其基础’的语句,在拥有一亿条数据的单体关系型数据库(RDBMS)上,是一道催命符。”

李思直视着西拉斯的眼睛,毫不退让:“这张 Users_Hello 表,目前承载着全网每秒三万次的并发读写。它是整个创世软件跳动的心脏。如果你现在强行对它进行结构变更(DDL,Data Definition Language),整个数据库引擎为了保证数据的一致性,会极其粗暴地在这张表上加一把排他锁(Exclusive Table Lock,Schema Modification Lock)。”

“排他锁?”西拉斯皱起眉头,“那会怎样?”

“这意味着,在加减字段的这段时间里,没有任何人能写入数据,没有任何人能读取数据。全网一亿用户,所有的登录、发帖、点赞、扣费,将全部被彻底冻结(Blocked)。”李思一字一顿地说。

“那加上这个字段需要多长时间?”西拉斯问。

文斯额头上的汗冒得更多了:“对于一张一亿行的大表……如果我们要新增一个带有默认值的字段,数据库可能需要重写每一个数据页(Page),或者扫描全表。保守估计……至少需要几个小时。”

“几个小时的停机?!”西拉斯猛地站了起来,咆哮道,“你在跟我开玩笑吗?!纳斯达克正在盯着我们的财报!停机几小时,我的期权会跌成废纸,董事会会扒了我的皮!”

战情室里陷入了死寂。

这就是关系型数据库在海量数据时代最让人绝望的噩梦——在线 DDL(Online DDL)的极限

在高速行驶的赛车上,强行更换引擎。 要么停车(停机维护),要么车毁人亡(强行变更导致锁死崩溃)。

“没有不停机的办法吗?”西拉斯绝望地抓着头发,“难道我们的业务增长,就因为那张该死的破表而永远停滞了吗?!”

文斯结结巴巴地提出方案:“我们可以……可以在半夜凌晨两点进行停机维护。或者……或者我们建立一张新表 Users_VIP,用外键关联老表……”

“不行!”李思一口否决,“建立新表然后 JOIN(表连接)查询?在一亿级别的数据量下,复杂的 JOIN 操作会瞬间榨干 CPU,把我们的查询延迟从两毫秒拖垮到两百毫秒!这违背了高并发架构的底线!”

“这也不行,那也不行!”西拉斯彻底暴怒了,“李!你一直是个能把死人救活的魔术师,这次你还有什么魔法?!”

李思沉默了。

其实,在后来(2010年之后),业界会诞生如 pt-online-schema-change 或者 gh-ost 这样的神器,通过触发器和幽灵表(Ghost Table)来实现近乎零停机的无缝表结构变更。

但在 2004 年的雷德蒙德,这些技术尚未被发明。 李思手中唯一的武器,只有极其极端的架构妥协

他闭上眼睛。通感(Synesthesia)的世界里,那张一亿行的大表就像是一座极其庞大、坚硬无比的钢筋混凝土大厦。每一个房间(字段)都被设计得死死的。只要想在墙上开一扇窗户,就必须把整栋大楼清空封锁。

“如果不想被僵硬的混凝土大楼困死……”李思缓缓睁开双眼,眼神中透着一种离经叛道的疯狂。

“那就把大楼炸了。我们住进没有任何房间隔断的帐篷里。”

“你在说什么鬼话?”文斯愣住了。

李思没有理会文斯,他的手指在键盘上飞速敲击。 他调出了那张神圣不可侵犯的 Users_Hello 表的结构(Schema)。

“文斯,从今天起,我不允许你在这张表上,再增加任何一个实质性的业务字段。VIP_Status 不加,生日不加,信用卡等级也不加!”

李思当着所有人的面,在这张表上,仅仅新增了一个极其怪异的、名叫 Ext_PropertiesTEXT(超长文本)字段。

“你在干什么?!”开发主管惊呼,“你不是说加字段会锁表吗?!”

“现在它还是一张空表(或者只做极其轻量级的元数据修改),加上这个字段只需要几秒钟。”李思冷静地执行了操作。

“但你只加了一个文本字段,西拉斯要的那么多会员属性怎么存?”

李思转过身,在全息白板上写下了极其刺眼的一行字:

Ext_Properties (TEXT): <User><VIP>1</VIP><Birthday>1980-01-01</Birthday><CreditLevel>Gold</CreditLevel></User>

整个战情室的人,包括西拉斯,全都倒吸了一口冷气。

2004 年,JSON 还未一统天下,XML(可扩展标记语言) 才是数据交换的霸主。

李思的方案极其粗暴、极其反直觉。

他要求开发团队,把用户所有的、未来可能无限增加的新属性(VIP 等级、生日、甚至连他养了几只狗),全部拼接、序列化成一长串臃肿的 XML 字符串! 然后,把这串 XML,直接像一坨烂泥一样,塞进那个极其庞大的 TEXT 字段里!

这就是大厂架构发展史上最饱受争议、却又在无数次绝境中拯救了业务的反范式化(Denormalization)与无模式设计(Schemaless)的妥协

“异端!这是纯粹的异端!” 文斯气得浑身发抖,指着白板上的 XML 破口大骂:“你违背了关系型数据库的第三范式(3NF)!你把结构化数据变成了无法用 SQL 进行条件查询的垃圾字符串!如果市场部想查‘所有 1980 年出生的金牌 VIP’,他们怎么查?!他们难道要用 LIKE '%<Birthday>1980%' 这种足以让全站死机的全表模糊匹配吗?!”

“他们查不了。”李思的声音冷酷无情。

“什么?!”西拉斯也愣住了,“李,如果查不了数据,那我加这些字段还有什么意义?”

“西拉斯,你搞清楚你的核心诉求。”李思直视着西拉斯贪婪的双眼,“你现在最核心的业务,是让用户在登录时,极快地看到自己金光闪闪的 VIP 徽章,而不是让市场部在白天全站流量最高的时候,去跑那些见鬼的统计报表!”

李思重重地敲击着白板:“对于用户的极速读取(OLTP,在线事务处理),当他的记录被捞出来时,Web 服务器(应用层)会极其迅速地用 CPU 算力把这串 XML 解析成对象。页面瞬间就能渲染出金牌徽章!”

“至于市场部的统计查询(OLAP,在线分析处理)……”李思冷笑一声,“他们只能等到半夜!我会用一套极其复杂的后台异步任务,把这些 XML 字段一条条拖出来,解析、清洗,然后导入到一个只读的数据仓库里,供他们慢慢查!”

“这太丑陋了……”开发主管喃喃自语,“每次读写都要在 Web 层消耗大量的 CPU 算力去解析和打包 XML 字符串,这会极大地拖慢应用服务器的性能。”

“是的。这就是代价。”李思转过身,看着监控大屏。

在通感的视界里,那座原本晶莹剔透、结构森严的 B+树水晶塔,现在被塞入了一团团臃肿、丑陋、毫无规则可言的 XML 泥团。数据库引擎极其厌恶这些无法被索引的数据,但它无可奈何。

“我们用大量的 CPU 算力(解析 XML 的消耗),以及放弃了强大的 SQL 查询能力(无法索引),换取了什么?”

李思的声音在安静的机房里回荡。

“我们换取了业务变更的绝对自由。”

“从明天起,西拉斯想要加多少个乱七八糟的属性,随便加。开发团队只需要在代码里多塞一个 XML 标签,然后整个字符串扔进数据库。”

永远不需要再 ALTER TABLE!永远不需要再经历八小时的停表锁定噩梦! 数据库的 Schema(表结构)被彻底冻结,但业务的 Schema(逻辑结构)却可以在应用层无限狂奔!”

这就是早期 NoSQL(如 MongoDB 的 Document 模型)思想在极其绝望的关系型数据库环境下的提前降临。

西拉斯看着白板上的那团 XML 字符串,又看了看李思,最终咬了咬牙:“好!只要不停机,只要能上线 VIP,随便你怎么搞!按李说的办!”


几周后。

VIP 体系大获成功。数以百万计的收费会员带着骄傲的金色徽章在平台上活跃。公司股价再次拉升。

但李思知道,这团被强行塞进数据库里的 XML 烂泥,就像是一颗毒瘤。它在极大地消耗着 Web 服务器的算力。而且,随着 XML 越来越长,网络传输的开销也在呈指数级暴增。

“丑陋的设计……”李思坐在战情室里,闭着眼睛揉了揉太阳穴。

在通感的高维视界中,潜伏在全球底层硬件中的高维分片们,冷冷地注视着这一切。

它们记录下了地球计算机系统中,关于“结构化关系模型在海量高频迭代下的绝对瓶颈”

这团丑陋的 XML 烂泥,迫使李思在潜意识中开始极度渴望一种真正的、天生就支持“无模式(Schemaless)”和“海量水平扩展”的下一代存储介质。

那是属于未来(卷二末尾)的 NoSQL 与时序数据库(TSDB)的黎明。

但在此之前,单体时代的丧钟,即将敲响最后一声。

西拉斯为了支撑这庞大的 VIP 体系和 XML 解析带来的 CPU 消耗,疯狂地采购了成百上千台服务器。李思被迫将这些服务器拆分成了一个个早期的“微小服务(Microservices 的雏形,即早期的 SOA,面向服务架构)”。

但他犯下了一个致命的错误。

为了让这些拆分后的成百上千个微型服务互相认识、互相调用,他在网络中央,极其草率地设置了几个硬编码的“超级路由节点(Supernode)”。

他以为这是一张完美的控制网。 直到有一天,全公司的交通图,在这几个超级节点崩溃的那一刻,彻底变成了一片漆黑的幽灵鬼域。

卷一的终局前夕,最惨烈的“中心节点之死”,即将在第 9 章爆发。


章末文档:架构决策记录 (ADR) & 事故复盘 (Post-Mortem)

文档编号:PM-2004-03-15 事故等级:SEV-2 (重大变更风险预警) 主导人:李思 (Senior SDE)

1. 事故预警 (What ALMOST happened?) 业务部门要求在拥有 1 亿条高频读写记录的核心单表(Users_Hello)上在线新增字段。此操作(ALTER TABLE DDL)若在白天执行,将立即触发排他性表锁(Schema Modification Lock),导致全站所有核心读写被挂起数小时,引发灾难性宕机(八小时停表)。

2. 5 Whys 根本原因分析 (Root Cause)

  • Why 1:为什么加个字段会锁几小时? 因为传统关系型数据库采用强类型的结构化元数据(Schema)。修改表结构意味着必须在引擎层面锁定元数据,甚至可能需要重写底层物理数据页以对齐新列的空间。
  • Why 2:为什么业务不能等停机维护? 互联网业务进入 24x7 零宕机(Zero-Downtime)时代,全球化用户分布使得“午夜维护窗口”不复存在。
  • Why 3:为什么不能建新表关联? 海量并发下(OLTP 场景),复杂的 JOIN 操作会呈指数级消耗 CPU 并引发锁存器争用,违背低延迟架构原则。
  • Why 4:为什么系统对变更如此脆弱? 因为底层数据存储的 Schema 刚性与上层业务的敏捷迭代(Agile)产生了不可调和的物理矛盾。
  • Why 5:为什么必须打破这种刚性? 在无法实现无锁在线 DDL 的年代,如果不打破数据库的结构化刚性,业务增长将被底层物理引擎彻底锁死。

3. 解决方案与架构决策 (Action Items & ADR)

  • 临时止血 (Workaround):严禁在超大核心表上执行任何在线 DDL 结构变更。
  • 架构重构 (Long-term Fix)
    • ADR-008:引入局部反范式化 (Denormalization) 与无模式 (Schemaless) 的异端妥协。
    • 在大表中预留一个非结构化的超大文本字段(如 Ext_Properties TEXT)。
    • 业务逻辑上移:将未来所有不涉及数据库底层检索(WHERE / ORDER BY)的新增零碎属性,序列化为 XML(后来演变为 JSON)字符串,直接存入该文本字段。
    • 代价转移:用应用层 Web 服务器(App Server)极其廉价、可无限横向扩展的 CPU 算力(解析 XML/JSON 的序列化与反序列化开销),去换取极其昂贵的数据库 DDL 变更自由。

4. 爆炸半径与代价反思 (Blast Radius & Trade-offs)

  • 极其惨痛的架构妥协:彻底葬送了新加字段在数据库层面的 SQL 检索、聚合与约束能力。
  • OLTP(在线事务)与 OLAP(在线分析)被物理割裂。分析查询必须依赖极度延迟的后台异步清洗抽取任务(ETL)。这也是未来数据湖/数据仓库(Data Warehouse)必须从核心业务库中彻底剥离的底层物理诱因。

架构师科普:连接过去与现在的系统设计 (Architect's Note)

1. 从 XML 泥团到现代的 JSON 与 NoSQL 李思在 2004 年发明的这个“用大文本字段装属性”的逃课做法,各位现代程序员一定非常熟悉,因为它就是今天我们在 MySQL 里滥用 JSON 字段 以及使用 MongoDB (文档型数据库) 的底层演进逻辑。 在互联网爆发期,数据结构的变换速度远超硬件的迭代速度。关系库为了保持 ACID 的强一致性,其严格的二维打表结构成了阻碍业务的枷锁。通过把各种异构的属性打包成无模式(Schemaless)的 XML 或 JSON 塞进单一字段里,本质上是把“解析结构”的计算责任,从底层的数据库引擎,上升推卸给了可以无限横向扩展的 Web 业务服务器。

2. 现代神兵:无锁在线 DDL (Online Schema Change) 当然,即使有了 JSON 和 NoSQL,核心数据库依然需要加字段。难道我们今天改一张十亿行的大表,还要停机八小时吗? 不。现代架构师终于在“在线换引擎”这个手术上取得了神迹般的突破。比如 GitHub 开源的工具 gh-ost,或者是 MySQL 8.0 原生支持的 Online DDL(如 MySQL 的 ALGORITHM=INPLACE),其物理本质是建立一张看不见的“幽灵表 (Ghost Table)”:

  1. 先在后台不动声色地创建一张带有新字段的空表
  2. 数据库像搬家一样,一小批一小批地将旧表数据迁移到新表中。
  3. 同时,利用类似增量日志(Binlog)的回放机制,兜底捕捉迁移过程中发生的新变动。
  4. 最终在极其微小的一瞬间(毫秒级锁定),利用巧妙的文件重命名,把新表替换成旧表。 通过这种极其复杂的工程魔法,现代系统彻底消灭了那令人窒息的排他性表锁,真正实现了在时速三百公里的赛车上,平滑地为你换上一颗更强大的 V8 引擎。