核心是数据库原生权限与连接约束双重卡死,必须精确授权、禁用高危权限、指定database参数、隔离数据源、审计SQL并监控异常访问。

多租户环境下 MySQL 的安全访问控制,核心就一条:**不靠应用层过滤,而靠数据库原生权限和连接约束双重卡死**。用 tenant_id 字段做隔离看似简单,但只要漏掉一处 WHERE 条件或权限收得松,数据就可能跨租户泄露。
GRANT 必须精确到 database.*,禁用全局权限
给租户账号授权时,GRANT SELECT, INSERT ON `tenant_a_prod`.* TO 'tenant_a_app'@'%' 是底线;任何带 *.* 的授权(比如 GRANT ALL PRIVILEGES ON *.*)都等于交出钥匙串。MySQL 默认允许用户看到所有库名,但看不到数据——前提是没给 SHOW DATABASES 权限。
- 创建账号后立刻执行:
REVOKE SHOW DATABASES ON *.* FROM 'tenant_a_app'@'%' - 5.7 无法用角色统一管理,必须写脚本批量 GRANT 并校验;8.0 推荐先
CREATE ROLE 'tenant_reader',再GRANT SELECT ON `tenant_%`.* TO 'tenant_reader',最后GRANT 'tenant_reader' TO 'tenant_a_app'@'%' - 绝对不要给租户账号
PROCESS、FILE、SUPER这类高危权限,哪怕只读场景也不行
连接串必须指定 database 参数,禁止 USE 切库
应用连接池初始化时,JDBC URL 里必须带 database=tenant_a_prod(如 jdbc:mysql://host:3306/?useSSL=false&database=tenant_a_prod)。如果只写 jdbc:mysql://host:3306/,再靠 USE tenant_a_prod 切库,等于把库名暴露在连接会话中,一旦连接被复用或泄露,风险陡增。
- ORM 框架(如 MyBatis、Hibernate)需配置多数据源,每个租户对应一个独立
DataSource,不能靠运行时动态切换database - 禁止在代码里拼接库名:
"SELECT * FROM " + tenantDb + ".orders"是硬编码注入点,MySQL 不会校验这个字符串是否真属于当前用户 - 备份脚本遍历租户库时,必须从白名单配置读取库名列表,而非执行
SHOW DATABASES动态获取
information_schema 泄露风险比想象中高
即使租户账号没 SHOW DATABASES 权限,它仍能查 information_schema.TABLES 和 information_schema.COLUMNS——只要表名已知。如果应用错误地把租户库名写死在 SQL 里(比如 SELECT COUNT(*) FROM tenant_b_prod.users),而该账号恰好有 SELECT 权限,那就能绕过库级隔离。
- 上线前必须审计所有 SQL:确认没有硬编码其他租户库名或表名
- 对敏感系统,可考虑关闭
information_schema的部分视图(MySQL 8.0+ 支持performance_schema替代方案) - 启用通用查询日志(
general_log)并监控异常库名访问,例如非本租户前缀的tenant_c_*出现在日志中
最易被忽略的其实是连接生命周期管理:连接池未清理旧连接、事务未及时提交导致会话残留、或者 ORM 自动重连时复用了错误的 database 上下文——这些不会报错,但会让隔离策略在静默中失效。


















