当单个数据库不再够用:分库分表与读写分离

当单个数据库不再够用:分库分表与读写分离

💡 原文英文,约5600词,阅读约需21分钟。
📝

内容提要

文章介绍 MySQL 分库分表与读写分离。分表用于分散单表数据,分库用于分散实例负载;可通过客户端或代理中间件路由,按范围或哈希分片。迁移宜采用双写加历史回填,按版本号防止覆盖,校验后切换。扩容宜预建逻辑分片,仅迁移物理位置。全局 ID 可用号段、UUID 或雪花算法。读写分离将写请求送主库、读请求送从库,需应对复制延迟,关键读走主库或等待 GTID。

🔎

延伸解读

分表不等于扩容:先分清瓶颈在存储还是算力

文章强调,分表把单表数据拆小,但若仍留在同一实例,CPU、内存和磁盘并未增加;分库把数据分散到不同实例,才真正增加处理资源。因此,遇到性能问题应先检查索引和 SQL,再根据数据增长、查询延迟和维护时间决定是否分片,而不是仅因行数达到某个阈值就分库分表。

双写迁移的关键:版本号与删除标记防覆盖

双写迁移期间,历史回填与新写入并发更新新库。文章指出,每次同步需携带主键和版本号,回填时若版本号不大于新库已有版本则保留新库记录,且比较与写入必须通过条件更新原子完成。删除也需保留带版本的删除标记,防止旧回填“复活”已删记录,校验通过后再清理标记。

预建逻辑分片:扩容只搬物理位置,不改路由规则

文章建议预先规划较多逻辑库表(如32库×32表),初期部署在少量实例上。扩容时按逻辑分片整组迁移到新实例,仅更新物理映射,ID的路由结果不变。这样避免每次扩容都改取模规则并重分布全部数据。但该方案只支持库级迁移,若需同库内不同表分到不同实例,中间件须支持表级数据源映射。

读写分离的延迟陷阱:关键读走主库或等GTID

复制延迟可能导致刚写入的数据在从库查不到。文章提醒,Seconds_Behind_Source为0也不保证某条刚提交事务可读;SELECT ... FOR UPDATE等需走主库。对读后写等关键流程,应显式路由到主库,或获取刚提交事务的GTID并等待目标从库执行后再查,同时设超时,超时则回主库或让调用方重试。

❓

Q&A

分库和分表到底有什么区别?分别解决什么问题?

分表是把一个表的数据拆到多个表里,减少单表数据量和索引大小,让查询更快;分库是把数据分散到不同的数据库实例上,增加CPU、磁盘等处理资源。分表解决单表过大问题,分库解决单实例负载过高问题。

数据库分片时,范围分片和哈希分片各有什么优缺点?

范围分片按连续区间(如按月)划分,范围查询容易路由,便于归档,但新数据可能集中写入造成热点;哈希分片按哈希值取模分配,数据分布均匀,但范围查询可能跨多个分片,且扩容时通常需要重新分布数据。

如何在不长时间停机的情况下把单库单表迁移到分库分表?

采用双写加历史回填:先部署双写,新数据同时写旧库和新库,查询仍走旧库;然后后台回填历史数据,每条同步操作带版本号,防止旧数据覆盖新数据;接着校验新旧数据一致性;最后切换读写路由到新库,并保留回滚窗口。

分库分表后如何设计全局唯一ID?

常见方案有:号段模式(数据库分配一段ID范围,节点本地消费)、UUID(本地生成但无序且较长)、雪花算法(64位,包含时间戳、节点ID和序列号,趋势递增)。雪花算法需注意节点ID唯一和时钟回拨问题。

MySQL读写分离中,主从延迟会导致什么问题?如何应对?

主从延迟会导致刚写入的数据在从库读不到,例如下单后立即查询可能查不到订单。应对方法包括:关键读走主库、使用GTID等待从库执行到指定事务、减少不必要的写后立即读、将报表查询分流到独立实例、通过分库分散写压力等。

分库分表后如何实现动态扩容?

建议预先规划较多的逻辑分片(如32个逻辑库,每个库32张表),初始部署在少量实例上。扩容时只需将部分逻辑分片整体迁移到新实例,更新物理映射即可,无需改变分片规则或重新分布所有数据。

🏷️

标签

➡️

继续阅读