freesql读写分离在ifreesql实例初始化时固化主从拓扑,必须单例注册于应用启动早期;select默认走从库,但insert后立即查询、for update、跨库join等场景需显式调用.master()强制主库,从库故障时自动隔离并降级主库。

FreeSql 的读写分离不是靠“切换 DbContext”或“动态改连接字符串”实现的,它在 IFreeSql 实例初始化时就固化了主从拓扑,后续所有查询自动按规则路由——用错时机或误判场景,90% 的问题都出在这儿。
如何正确初始化带读写分离的 IFreeSql 实例
必须在应用启动早期(如 ASP.NET Core 的 Program.cs 或 DI 容器注册阶段)一次性构建单例 IFreeSql,不能每次请求都 new 一个。否则从库权重、故障隔离、连接池复用等机制全部失效。
常见错误写法:new FreeSqlBuilder().UseConnectionString(...).UseSlave(...).Build() 写在仓储方法里、或者每次查库前调用。
正确做法(推荐 DI 注册):
var fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, "Server=master;Database=test;...")
.UseSlave("Server=slave1;Database=test;...", "Server=slave2;Database=test;...")
.UseSlaveWeight(10, 20) // 可选:slave1 接收 1/3 流量,slave2 接收 2/3
.Build();
services.AddSingleton<ifreesql>(fsql);</ifreesql>
- 务必使用
Singleton生命周期,IFreeSql内部管理连接池、从库健康状态、权重调度器 -
.UseSlave()支持传入多个连接字符串,也可传入IEnumerable<string></string> - 不调用
.UseSlave()就没有读写分离,所有 SELECT 默认走主库 - MySQL 连接字符串中建议显式加
SslMode=none,避免从库因 SSL 握手失败被误判为宕机
Select<t>()</t> 默认走从库,但这些情况必须强制走主库
FreeSql 对 SQL 类型做静态识别:SELECT 开头且不含 FOR UPDATE、LOCK IN SHARE MODE、WITH (UPDLOCK) 等锁提示时,才发往从库。一旦识别到写语义或显式锁,自动回落主库。
但有些业务逻辑无法被自动识别,必须手动干预:
- 刚 INSERT 后立刻 SELECT(防止主从延迟导致查不到)→ 用
.Master() - 需要
SELECT ... FOR UPDATE做悲观锁 → 必须加.Master(),否则报错或锁失效 - 跨库 JOIN 查询(主库表 + 从库表)→ FreeSql 不支持,一律走主库,且需手动确保从库数据已同步
- 调用存储过程且过程内含写操作 → 即使语句以 SELECT 开头,也应加
.Master()
示例:
var user = fsql.Select<user>()
.Where(a => a.Id == 123)
.Master() // 强制主库,规避延迟
.ToOne();</user>
从库故障时怎么 fallback?关键看隔离策略和重试间隔
FreeSql 不是“试一次连不上就换下一个”,而是把不可用从库加入隔离列表,并按固定周期(默认 30 秒)探活。期间所有本该发给它的读请求,直接分给其他可用从库;若全部从库都被隔离,则所有 SELECT 自动降级走主库。
这个行为不可关闭,但可调参:
-
.UseSlaveHealthCheck(true):启用健康检查(默认开启) -
.UseSlaveHealthCheckInterval(TimeSpan.FromSeconds(10)):缩短探活间隔,适合对故障敏感的场景 - 隔离状态存在内存中,应用重启后重置 —— 所以不要依赖“重启恢复从库”来绕过问题
- 日志里出现
Slave connection failed: ... will be isolated表示已触发隔离,不是 bug 是预期行为
为什么加了 .UseSlave() 但 Select<t>()</t> 还是走主库?
最常见三个原因:
- 没用单例:每次查库都 new
IFreeSql,从库配置丢失 - 连接字符串格式错误:比如 MySQL 从库少写了
Database=xxx,导致fsql.Ado.ExecuteConnectTest()初始化失败,整个UseSlave配置被静默跳过 - 实体类没打
[Table]或[Column(IsPrimary = true)],FreeSql 无法生成合法 SQL,回退到主库兜底执行(此时日志会输出警告)
验证是否生效最简单的方法:
fsql.Ado.UseMonitorCommand(cmd => Console.WriteLine($"[SQL] {cmd.CommandText} | DataSource: {cmd.Connection.DataSource}"));
运行一个 Select<t>().ToList()</t>,看日志里 DataSource 是 master 还是 slave 地址 —— 别猜,直接看输出。
真正麻烦的从来不是配置多难,而是主从延迟、锁语义、故障恢复节奏这些隐性约束没被显式处理。FreeSql 把路由逻辑藏得够深,反而让人忘了:SELECT 走哪台机器,本质是个业务一致性决策,不是技术开关。











