<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>数据库 on Zampo Blog</title><link>https://blog.cpdd.fyi/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/</link><description>Recent content in 数据库 on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Wed, 15 Jul 2026 12:00:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/index.xml" rel="self" type="application/rss+xml"/><item><title>MySQL 开始分库分表时，别急着换国产数据库：先问完这五句</title><link>https://blog.cpdd.fyi/posts/mysql-sharding-domestic-database-five-questions/</link><pubDate>Wed, 15 Jul 2026 12:00:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/mysql-sharding-domestic-database-five-questions/</guid><description>&lt;p&gt;一个 MySQL 集群开始不好伺候，团队通常会经历同一串动作：加只读副本、拆热点表、按用户 ID 分库、路由写进中间件、跨库查询单独做服务。&lt;/p&gt;
&lt;p&gt;最开始都觉得还能扛。&lt;/p&gt;
&lt;p&gt;直到某次需求只是“查一下用户在所有店铺的订单”，代码评审里先出现的不是 SQL，而是“这个表到底落在哪个库”“能不能接受异步汇总”“跨库事务谁兜底”。这时候，“换国产数据库”会突然变成一个很有诱惑力的选项。&lt;/p&gt;
&lt;p&gt;但它不是止痛药。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;国产数据库不是让 MySQL 原地变强；它是在替你接走一部分扩展、可用性和兼容性问题，同时把另一部分复杂度交还给你。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这篇不列厂商，也不讲谁排第几。只把一个只用过 MySQL 的开发者最该问的五句话讲清楚：它是什么，能干什么，你为什么可能需要它，代价落在哪里，最后又到底能换回什么。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;【配图占位 1】单库 → 应用层分片 → 多节点数据层。突出“复杂度没有消失，只是换了落点”。详见配图 brief。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="1-这是啥"&gt;1. 这是啥&lt;/h2&gt;
&lt;p&gt;一句话：&lt;strong&gt;国产数据库是一组由本土厂商交付的关系数据库产品；对应用来说仍然是表、SQL、索引和事务，对团队来说换的是兼容边界、扩展方式和运维模型。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;别把“国产”理解成一种新的数据库原理。&lt;/p&gt;
&lt;p&gt;你不会因为换了它，就突然不用写索引、不用看慢查询、不用处理死锁。业务代码照样要连数据库，ORM 照样会生成 SQL，线上照样会有大事务和误删风险。它首先是一个产品与交付选择，不是一种脱离关系数据库常识的新技术。&lt;/p&gt;
&lt;p&gt;真正容易误解的是后半句。&lt;/p&gt;
&lt;p&gt;MySQL 用久了，很多人默认的世界观是：一份数据主要待在一台主库上；容量不够，先加机器；读不够，拉副本；再不够，就由应用决定数据去哪张表、哪个库。&lt;/p&gt;
&lt;p&gt;而你考虑换的这类产品，常常会把“数据能不能放到多台机器上”“一台机器故障后谁接手”“应用看到的还是不是一张表”这些事，往数据库内部收一部分。&lt;/p&gt;
&lt;p&gt;这不代表它替你做完了架构。&lt;/p&gt;
&lt;p&gt;它只是把原来散在路由层、同步任务、脚本和人工操作里的规则，换成了数据库自己的规则。规则放的位置变了，责任没有消失。&lt;/p&gt;
&lt;h2 id="2-干什么的"&gt;2. 干什么的&lt;/h2&gt;
&lt;p&gt;和单机 MySQL 相比，开发者真正能感知到的变化，通常不是某个宣传名词，而是下面几件很具体的事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据可以跨机器放。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;以前一张大表顶到单机磁盘、CPU 或写入上限，你的动作往往是拆库拆表。拆完以后，&lt;code&gt;orders_17&lt;/code&gt; 和 &lt;code&gt;orders_18&lt;/code&gt; 在应用眼里已经不是一张表了：路由要算，聚合要补，运维要记住每个库的容量。&lt;/p&gt;
&lt;p&gt;有些数据库可以把一张逻辑表拆成多个数据分区，放到不同机器上。应用仍然面对一张表，数据到底在哪台机器，由系统按规则处理。你少写了一层分片路由，至少不用每次业务查询都先问“落点在哪”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事务可能跨节点。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;分库之后，最难受的不一定是查询，而是本来一句 &lt;code&gt;BEGIN&lt;/code&gt; 能解决的事情，忽然要面对多个库。你可以改成最终一致，也可以上补偿和对账；这都是工程方案，但每种都要把失败路径补齐。&lt;/p&gt;
&lt;p&gt;支持跨节点事务的数据库，能让一部分原本跨库的写入仍在数据库事务语义里完成。这里的关键词是“一部分”。数据一旦不在同一个节点，协调、网络往返和副本确认都会进账单。它能让你少造轮子，不会让物理成本归零。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;高可用的动作更靠近数据库。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;单主、主从、故障切换、读写漂移，这些词 MySQL 团队都不陌生。差别在于，有些产品会把副本、故障检测、切换和数据重建做得更内聚。应用不必为每一个节点故障临时拼一套流程。&lt;/p&gt;
&lt;p&gt;但“更内聚”不等于“你不用管”。副本放在哪个故障域、切换时能丢多少数据、连接池多久感知、下游能不能承受短暂失败，仍然是你的系统问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;扩容不再总是一次业务改造。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;分库分表最贵的部分，往往不是第一次拆，而是第二次、第三次扩容。库数量从 16 变成 32 时，路由规则、历史数据、离线任务、报表口径都可能要动。&lt;/p&gt;
&lt;p&gt;如果数据库能把数据分布与重平衡接住，扩容会更像一次受控的基础设施操作，而不是让业务团队改一轮代码。注意，是“更像”，不是“点一下按钮就结束”。热点、容量、水位和迁移窗口还是要盯。&lt;/p&gt;
&lt;p&gt;这些能力可以压成一句：&lt;strong&gt;它想把你已经塞进应用层的数据分布问题，往数据库里收回去。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="3-我为什么要用它"&gt;3. 我为什么要用它&lt;/h2&gt;
&lt;p&gt;不是因为“分布式听起来更高级”，更不是因为某个采购词。&lt;/p&gt;
&lt;p&gt;你该考虑它，通常是因为 MySQL 的使用方式已经开始反过来塑造业务代码，而且这种塑造越来越贵。&lt;/p&gt;</description></item><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><item><title>PostgreSQL 18：异步 I/O 不光是快 3 倍</title><link>https://blog.cpdd.fyi/posts/postgresql-18-new-features/</link><pubDate>Wed, 08 Jul 2026 10:00:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/postgresql-18-new-features/</guid><description>&lt;p&gt;PG 大版本升级，过去一直有两件烦心事：&lt;/p&gt;
&lt;p&gt;一是升完了统计信息丢光，优化器两眼一抹黑，ANALYZE 跑完之前查询崩好几天。二是折腾半天，性能提升不明显，不值得冒这个险。&lt;/p&gt;
&lt;p&gt;PG 18 把这两件事都动了。&lt;/p&gt;
&lt;p&gt;而且这次不是小步迭代——几个改动直接改了 PG 用了二十年的工作方式。&lt;/p&gt;
&lt;h2 id="最大的改法异步-io"&gt;最大的改法：异步 I/O&lt;/h2&gt;
&lt;p&gt;PG 以前读数据是这样的：发一个 I/O 请求，进程挂起等数据回来，再发下一个。这在机械硬盘时代是自然的——磁盘自己就是串行的。但在 NVMe SSD 面前，CPU 等磁盘的时间越来越不值。&lt;/p&gt;
&lt;p&gt;PG 18 引入了异步 I/O 子系统。一次发一堆请求，谁先回来谁先处理，CPU 不再空等。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/postgresql-18-new-features/inline-01.png" alt="异步 I/O 原理对比"&gt;&lt;/p&gt;
&lt;p&gt;官方数据：顺序扫描在某些场景下能到 &lt;strong&gt;3 倍&lt;/strong&gt;。不是 30%，是 3 倍。&lt;/p&gt;
&lt;p&gt;怎么开？一行配置：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;io_method = &amp;#39;io_uring&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;io_uring&lt;/code&gt; 是 Linux 5.1+ 的高性能异步接口。内核版本不够也可以用 &lt;code&gt;worker&lt;/code&gt; 模式——走工作线程模拟异步，差一点但比老模式强。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这个改动最值钱的地方：&lt;/strong&gt; 不是跑测试快 3 倍，而是你那些吃顺序扫描的场景——数仓 ETL、全表报表、大表 vacuum、bitmap 扫描——都能吃上。如果业务 IO 密集，升了立竿见影。&lt;/p&gt;
&lt;h2 id="uuidv7b-tree-不再怕随机-id"&gt;UUIDv7：B-tree 不再怕随机 ID&lt;/h2&gt;
&lt;p&gt;UUID 做分布式主键的好处不用多说：全局唯一、不依赖自增。但 UUIDv4 是纯随机的，往 B-tree 里写的时候，每个新值可能落在任何位置。结果是什么？索引页频繁分裂，写入性能崩得比不用 UUID 还惨。&lt;/p&gt;
&lt;p&gt;好多团队因为这个原因又退回自增了。&lt;/p&gt;
&lt;p&gt;PG 18 原生支持 UUIDv7——时间戳在前，保证新生成的 UUID 大致有序。B-tree 写入不再分裂。&lt;/p&gt;</description></item></channel></rss>