go语言无原生分库分表能力,需手动实现分片键提取与路由计算;扩容须用一致性哈希或“分片键+时间”双维度;多库连接需预建独立sql.db实例并单独配置连接池;跨库join和排序须在应用层合并处理。

分库分表不是 Go 语言原生能力,得自己写路由逻辑
Go 没有像 Java 的 ShardingSphere 或 Python 的 django-sharding 那样的开箱即用分库分表框架。所有路由决策——比如 user_id=12345 落到哪个库、哪张表——必须由你显式编码实现。核心就两步:提取分片键(shard key),再套哈希/取模/范围规则算出目标 db_name 和 table_name。别指望 ORM 自动帮你分,gorm 或 sqlx 连多数据库连接都得手动管理。
用 hash % N 做分表时,扩容会丢数据
常见错误是直接对分片键做 hash(user_id) % 4 分 4 张表,上线后发现容量不够,想扩到 8 张——但旧数据不会自动重分布,查 user_id=100 可能去错表。真正可用的方案只有两种:
- 用一致性哈希(如
github.com/cespare/xxhash+github.com/sony/sonyflake配合环形结构),增减节点只影响邻近少量数据 - 改用“分片字段 + 时间”双维度,例如按
user_id % 16分库,再按created_at月分表(user_202407),扩容时只建新表,老数据不动 - 强制要求所有查询带分片键,否则拒绝执行——避免全库扫描拖垮系统
database/sql 不支持动态切换 DB,得自己维护连接池
Go 的 sql.DB 是单库抽象,没法在一次事务里跨库操作。实际做法是预先创建多个 *sql.DB 实例,按分片规则索引:
// 伪代码:按 user_id % 4 分 4 个库
var dbMap = map[int]*sql.DB{
0: sql.Open("mysql", "user0:pass@tcp(10.0.0.1:3306)/db0"),
1: sql.Open("mysql", "user1:pass@tcp(10.0.0.2:3306)/db1"),
// ...
}
shard := int(hash(userID)) % 4
db := dbMap[shard]
rows, _ := db.Query("SELECT * FROM user_"+strconv.Itoa(shard)+" WHERE id = ?", userID)
注意:每个 *sql.DB 都要单独调 SetMaxOpenConns,别共用一个连接池;事务只能在单个 db 内生效,跨库更新得用 TCC 或本地消息表补偿。
分库后 JOIN 和全局 ORDER BY 必须收口到应用层
MySQL 不支持跨库 JOIN,也不能保证跨多个 *sql.DB 查询结果的全局顺序。典型场景如“查最近 100 个订单”,得:
- 并发查所有分库的
orders_202407表,各自取前 100 条 - 在 Go 里用
heap或sort.Slice合并排序,再截取 top 100 - 分页慎用
LIMIT offset, size,offset 越大越慢——改用游标(WHERE created_at ) - 唯一 ID 别用
AUTO_INCREMENT,改用sonyflake或twitter/snowflake生成全局有序 ID
最麻烦的其实是分布式事务和跨库外键约束,生产环境基本靠业务妥协绕过,而不是硬上两阶段提交。











