<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>B+Tree on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/b+tree/</link><description>Recent content in B+Tree on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Fri, 10 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/b+tree/index.xml" rel="self" type="application/rss+xml"/><item><title>你写了个日志系统选了 InnoDB，然后发现写入越来越慢</title><link>https://blog.cpdd.fyi/posts/lsm-vs-btree-storage-engine-decision/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0000</pubDate><guid>https://blog.cpdd.fyi/posts/lsm-vs-btree-storage-engine-decision/</guid><description>&lt;p&gt;你做了一个日志系统。&lt;/p&gt;
&lt;p&gt;数据量每天 500GB，全是 APP 行为埋点。你选了 MySQL InnoDB，因为团队最熟。跑了一个月，写入越来越慢，到了夜里还要锁表清理。DBA 说：要不试试换 RocksDB？&lt;/p&gt;
&lt;p&gt;你换了一版。写入快了很多，但查询偶尔会卡一下，有时候 p99 飙到 200ms+。老板问为什么，你说「Compaction 抖了一下」。&lt;/p&gt;
&lt;p&gt;然后你开始琢磨：InnoDB 和 RocksDB，底层不都是「树」吗？怎么差异这么大？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;B+Tree 和 LSM-Tree 不是「哪个更快」的问题，是「你把写放大的代价付在什么时候」的问题。&lt;/strong&gt; 前者付在每次写入，后者付在后台合并。你的业务承受哪种代价，就选哪种设计。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="先解决一个问题btree-在写入时到底发生了什么"&gt;先解决一个问题：B+Tree 在写入时到底发生了什么&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/lsm-vs-btree-storage-engine-decision/inline-01-btree-split.png" alt="B+Tree 页分裂示意图"&gt;&lt;/p&gt;
&lt;p&gt;大多数后端工程师都用过 MySQL InnoDB，知道数据存在索引里，索引是 B+Tree。但到底怎么「写进去」的，大部分人没细想过。&lt;/p&gt;
&lt;p&gt;B+Tree 的结构简化版是这样的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有数据行（value）只存在叶子节点&lt;/li&gt;
&lt;li&gt;内部节点只存 key，不存数据，作用是导航&lt;/li&gt;
&lt;li&gt;叶子节点之间用双向链表连接，方便范围扫描&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当你 INSERT 一行数据，InnoDB 做的事是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从 root 开始，顺着内部节点往下走到对应叶子页&lt;/li&gt;
&lt;li&gt;把这一页从磁盘读到 Buffer Pool（如果不在内存里）&lt;/li&gt;
&lt;li&gt;在页内的有序位置插入&lt;/li&gt;
&lt;li&gt;如果页满了，分裂成两页，重新分布键值&lt;/li&gt;
&lt;li&gt;如果分裂传到上层，级联更新内部节点&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;问题出在第 4 步。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;你用自增 ID 做 PK 时，新插入的数据永远往最右侧叶子节点写，只有在最右侧满了才触发分裂，而且只影响右边界。&lt;/p&gt;
&lt;p&gt;但是你用了 UUID 做 PK——新人常犯的经典错误——或者业务 key 本身就是分散的，那么每次插入的数据会随机落到任意一个叶子节点。&lt;strong&gt;每次插入都可能触发一次页分裂。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;页分裂的代价是什么？InnoDB 默认页大小是 16KB。你写入一行 128 字节的数据，如果触发了页分裂，InnoDB 要读旧页、写两页，刷盘的数据可能是 32KB+。你写的是 128 字节，硬盘却干了 32KB 的活。&lt;strong&gt;写放大。&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>