Schema隔离本身不能防SQL注入,但能大幅收窄注入成功后的破坏半径——前提是权限控制同步落地;只建Schema不设权限等于开门揖盗,因Schema仅是命名空间而非访问壁垒,真正的隔离依赖账号级细粒度权限。

Schema隔离本身不能防SQL注入,但能大幅收窄注入成功后的破坏半径——前提是权限控制同步落地。
为什么只建Schema不设权限等于开门揖盗
很多团队在MySQL或PostgreSQL里为每个租户建了tenant_a、tenant_b这样的Schema,就以为数据安全了。实际只要应用层用了超级账号(如root或db_owner),攻击者通过SQL注入拿到连接后,仍可跨Schema查表:SELECT * FROM tenant_b.users、DROP SCHEMA tenant_c全都能执行。
Schema只是命名空间,不是访问壁垒。真正的隔离靠的是数据库账号的权限粒度。
- 必须为每个租户创建专用数据库用户,例如
user_tenant_a - 该用户仅授予对应Schema的
USAGE、SELECT、INSERT、UPDATE权限,禁用CREATE、DROP、ALTER - 禁止任何跨Schema的
GRANT,尤其不要给public角色赋权
PostgreSQL中RLS + Schema组合的实操要点
PostgreSQL支持Schema级隔离叠加行级安全策略(RLS),这是目前最稳健的混合方案。但要注意几个硬性约束:
- RLS函数里不能直接引用
CURRENT_SCHEMA来动态判断租户,因为search_path可能被篡改;应优先用SESSION_CONTEXT('tenant_id')或current_setting('app.tenant_id') - 所有业务表必须显式启用
ENABLE ROW LEVEL SECURITY,否则RLS策略不生效 - 若用
SET search_path TO tenant_a切换上下文,需确保连接池每次归还前重置search_path,否则下一个租户可能误用上一个的Schema - DDL操作(如加字段)必须在对应Schema下执行,脚本里不能写死
public.users,而要动态拼接tenant_x.users
MySQL中模拟Schema隔离的真实限制
MySQL没有原生Schema权限模型,CREATE DATABASE和CREATE SCHEMA等价,但用户权限只能按库(database)授予,无法细化到“某个库下的某几张表”。这意味着:
- 你建了
tenant_a库,给用户u_a授了tenant_a.*权限,他就能看到并操作这个库里所有表——包括你后来手动加的审计表、日志表 - 无法阻止用户执行
SHOW TABLES FROM tenant_b(如果他碰巧知道库名且有SELECT权限) - 不支持RLS,只能靠应用层拦截或视图封装,风险更高
- 备份脚本若未限定库名,
mysqldump --all-databases会把所有租户库一起导出,运维极易误操作
最容易被忽略的三个执行细节
Schema隔离方案上线后,真正出问题的地方往往不在设计文档里,而在日常执行链路上:
- DBA手工执行SQL时,习惯性连到
root账号,哪怕只查一条SELECT,也可能因未指定USE tenant_x而默认进information_schema或mysql系统库,暴露元数据 - 报表类查询常需跨租户聚合,这类宽表若放在公共Schema(如
reporting)里,必须用视图+CHECK OPTION限制可访问范围,不能直连底层租户表 - 连接池初始化时若未设置
search_path或database参数,首次查询可能落到publicSchema,导致relation not found错误或误查默认表











