必须从context.Context取,不能从X-Tenant-ID或query参数二次解析;tenant_id须在认证中间件中从JWT payload解出并校验后,用私有key注入context,后续全部通过ctx.Value(tenantKey{})获取。

tenant_id从context取还是从header取?
必须从 context.Context 取,不能从 X-Tenant-ID 或 query 参数二次解析。前端传的任何值都不可信,一旦中间件没校验或跳过,后续所有 DAO、缓存、日志都会错乱。
正确做法是:在认证中间件里从 JWT payload 解出 tenant_id 和角色,做格式校验(长度 ≤ 64、只含字母数字和短横线),然后用私有 key 注入 context:
type tenantKey struct{}
ctx = context.WithValue(ctx, tenantKey{}, tenantID)
后续 handler 全部用 ctx.Value(tenantKey{}) 拿,别再读 header。
多个goroutine共用一个*sql.DB安全吗?
共享数据库 + tenant_id 字段隔离时,单个 *sql.DB 实例是安全的——前提是连接池参数合理、所有 SQL 都显式带 WHERE tenant_id = ?。
立即学习“go语言免费学习笔记(深入)”;
但 schema 隔离下,**绝对不安全**:database/sql 连接池不保留 session 状态,SET search_path TO tenant_abc 的效果不会延续到下一次复用连接。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 错误做法:全局一个
*sql.DB,每次查询前db.Exec("SET search_path...") - 正确做法:用
sync.Map缓存tenant_id → *sql.DB,每个租户独占连接池,并设db.SetMaxOpenConns(5)防止 PG OOM
GORM Preload和Count为什么总绕过租户过滤?
db.Preload("Orders").Find(&u) 和 db.Model(&Order{}).Count(&count) 默认不继承主查询的 scope 或 context,也不会自动注入 tenant_id 条件。
这不是 GORM bug,是设计使然——它只对当前语句生效,关联和统计需显式约束:
db.Preload("Orders", func(db *gorm.DB) *gorm.DB { return db.Where("tenant_id = ?", tenantID) })db.Where("tenant_id = ? AND status = ?", tenantID, "pending").Model(&Order{}).Count(&count)- 禁用
Unscoped(),除非你明确知道它会跳过所有租户条件
缓存键漏 tenant_id 会导致什么?
不是“偶尔错”,而是确定性越权:两个租户查同名配置 config,Redis 里只存一份,谁先写谁赢,后查的直接拿到前一个租户的数据。
硬编码拼接是最稳的:
"tenant:" + tenantID + ":config" "tenant:" + tenantID + ":user:" + userID
别用 struct hash 或 JSON 序列化生成 key——字段顺序、tag 差异、nil 值处理都会导致键不一致;也别在 cache miss 后忘记把带 tenant_id 的结果回填,否则下一个租户就命中脏数据。

















