能实现租户隔离,但需严格满足三项条件:grant权限必须带. *后缀并用反引号包裹库名、显式收回show databases权限、连接串强制指定database参数,缺一不可。

能,但必须把 GRANT 写对、SHOW DATABASES 收紧、连接串写死 database 参数——漏掉任意一项,隔离就失效。
GRANT 必须带 .* 后缀,且库名加反引号
常见错误是写成 GRANT SELECT ON tenant_abc TO 'u1'@'%',这权限根本不会生效。MySQL 权限系统只认 tenant_abc.* 这种格式;单独写库名会被当成全局对象,而你没授过全局权限,结果连 USE tenant_abc 都被拒绝。
-
GRANT SELECT, INSERT, UPDATE, DELETE ON `tenant_abc`.* TO 'tenant_abc_app'@'10.20.30.%'—— 反引号必须加,尤其当库名含短横线(如`tenant-123`)或数字开头时 - host 尽量不用
'%',限制到子网更安全(如'10.20.30.%') - 执行完不用
FLUSH PRIVILEGES—— 正常GRANT会自动刷新内存缓存
必须显式收回 SHOW DATABASES 权限
即使账号只能访问 tenant_x 库,默认仍能在 SHOW DATABASES 里看到所有库名。这不是配置失误,而是 MySQL 的设计:只要账号存在且有任意库权限,SHOW DATABASES 就列出全部库(除非用 ProxySQL 过滤,或 MySQL 8.0+ 配 information_schema_restricted)。
- 补救命令:
REVOKE SHOW DATABASES ON *.* FROM 'tenant_x_app'@'10.20.30.%' -
REVOKE ALL ON *.*不包含SHOW DATABASES,这个权限得单独收 - MySQL 5.7 及更早版本无法真正隐藏库名,只能靠应用层不展示
SHOW DATABASES结果
连接时必须强制指定 database 参数
很多 ORM 或连接池(如 HikariCP)默认不设初始库,靠运行时执行 USE tenant_x。这在多租户下极危险:连接复用时状态残留,或某次查询漏写库前缀,就可能读到同名表的其他租户数据。
- JDBC 连接串中加:
?useSSL=false&database=tenant_x - Druid 可配
connectionInitSqls=["USE tenant_x"],但需确保每次获取连接都重置 - HikariCP 没内置机制,得用
ConnectionCustomizer.onAcquire手动执行USE tenant_x - 绝对不要在 SQL 里拼库名:
"SELECT * FROM " + tenantDb + ".orders"是注入高危点
建库建号必须由专用服务统一执行
不能让租户自建库,也不能用 DBA 账号手动操作。建库后立刻绑定专属账号、收回 CREATE DATABASE 和 DROP DATABASE 权限,否则一个租户建个 mysql 同名库就能引发异常。
- 建库语句必须显式指定字符集:
CREATE DATABASE `tenant_123` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci - 租户库名强制规范(如
tenant_{id}),避免下划线以外符号,防止 SQL 拼接注入 - 禁用直接改
mysql.db表的方式批量开权限——MySQL 5.7+ 权限缓存机制会让手动改表后不立即生效,还可能因结构变更导致异常
最易被忽略的是:权限检查发生在语句解析之后,如果库名来自用户输入且未校验,SELECT * FROM tenant_x.users 中的 tenant_x 被替换成别的库名,MySQL 根本不会拦——它只认最终解析出的库表名。











