分库分表需手动实现路由、连接、事务与查询拼装,ORM无法自动支持;按租户分库须在请求初期确定连接串并隔离存储;按时间分表需动态拼接表名且严防SQL注入;跨库事务应采用Saga模式而非TransactionScope;跨分表分页须避免全表扫描,改用汇总索引或游标分页。

分库分表不是 ORM 能自动解决的问题
Entity Framework 或 Dapper 本身不提供分库分表能力。它们面向单个连接字符串和单个逻辑数据库,一旦你把数据拆到多个物理库或多个表,就得自己控制路由、连接、事务和查询拼装。硬套 EF 的 DbContext 会踩坑:比如跨库 JOIN 失败、迁移脚本失效、SaveChanges() 写错库、分布式 ID 冲突。
按租户 ID 分库必须手动选连接字符串
典型 SaaS 场景下,每个租户一个库(tenant_001、tenant_002),关键在请求进来时就确定用哪个库。不能等到执行 SQL 才决定——那会破坏连接复用和事务边界。
- 用中间件或过滤器从请求头/路由/claims 中提取
tenantId - 查配置中心或内存字典,映射到具体连接字符串,存入
AsyncLocal或HttpContext.Items - 自定义
IDbConnection工厂,每次调用Open()前读取当前连接串 - 避免在静态类里缓存连接对象——不同租户会互相污染
示例片段:
public IDbConnection CreateConnection() => new SqlConnection(GetCurrentConnectionString());
按时间分表必须重写 INSERT 和 SELECT 的表名
比如日志表按月切分:log_202401、log_202402。Dapper 可以拼接表名,EF 则几乎无法原生支持(除非用原始 SQL + FromSqlRaw)。
- 写入时,根据
DateTime.Now或业务时间字段算出目标表名,再拼进 SQL - 查询时,若查单月,直接定位表;若查跨月,得 UNION ALL 多个表,或改用 ElasticSearch 等外部方案
- 别用 EF 的
[Table("log_{yyyyMM}")]—— 属性值是编译期常量,不会动态变 - 注意 SQL 注入:表名必须白名单校验,不能直接插用户输入
跨库事务只能靠 Saga 或最终一致性
.NET 的 TransactionScope 在跨多个 SQL Server 实例时默认降级为本地事务,且性能极差。真要强一致,得放弃两阶段提交。
- 用状态机 + 补偿操作实现 Saga:比如“扣库存”成功后,“减余额”失败,则触发“加回库存”
- 写入时先落本地消息表(同一库),再由后台服务投递到其他库,失败则重试+告警
- 避免在 HTTP 请求内同步调多个库的写操作——超时、锁等待、网络抖动都会放大问题
最容易被忽略的是分页:跨分表查询时,OFFSET 10000 ROWS 会扫所有子表,必须收拢到汇总索引表或改用游标分页。











