不能用 grant all on . 给租户账号,因其赋予全局权限,导致可访问所有库、删任意表、创建用户,彻底破坏租户隔离;正确做法是仅授予目标库(如 tenant_a_prod.*)的最小必要权限,并显式 revoke show databases 等高危权限。

为什么不能用 GRANT ALL ON *.* 给租户账号
这是最常见也最危险的误操作。一旦执行 GRANT ALL ON *.* TO 'tenant_a_app'@'%',该账号就能访问所有库、删任意表、甚至创建新用户——完全破坏租户边界。MySQL 8.0 的权限验证是“先匹配到就停止”,如果 user 表里已有全局权限,db 表里的库级限制根本不会生效。
真正安全的做法是:只授具体库的权限,且显式收回全局权限项。例如:
CREATE USER 'tenant_a_app'@'%' IDENTIFIED BY 'pwd123'; GRANT SELECT, INSERT, UPDATE, DELETE ON `tenant_a_prod`.* TO 'tenant_a_app'@'%'; REVOKE SHOW DATABASES ON *.* FROM 'tenant_a_app'@'%'; FLUSH PRIVILEGES;
-
SHOW DATABASES必须收回,否则SHOW DATABASES会暴露其他租户库名 - 不要依赖
USAGE默认权限——它不阻止SHOW DATABASES,必须显式REVOKE - MySQL 8.0 不再需要
FLUSH PRIVILEGES强制刷新,但执行后可确认权限已落盘
如何用角色(CREATE ROLE)统一管理租户权限模板
在多租户系统中,每个租户都需要一套相似但隔离的权限(如只读、读写、带备份权限)。手动对每个账号重复 GRANT 容易出错、难维护。MySQL 8.0 的角色机制就是为此设计的。
推荐做法:建两个角色,再绑定到具体用户:
CREATE ROLE 'tenant_reader', 'tenant_writer'; GRANT SELECT ON `tenant_%`.* TO 'tenant_reader'; GRANT SELECT, INSERT, UPDATE, DELETE ON `tenant_%`.* TO 'tenant_writer'; GRANT 'tenant_reader' TO 'tenant_a_app'@'%'; SET DEFAULT ROLE 'tenant_reader' TO 'tenant_a_app'@'%';
- 注意:角色权限中的
`tenant_%`是通配符,仅在 MySQL 8.0.16+ 支持;低版本需为每个库单独授权 - 角色本身不拥有连接能力,必须
GRANT角色给用户,并用SET DEFAULT ROLE激活 - 后期调整权限(如加
EXECUTE)只需改角色,所有绑定用户自动继承,无需逐个GRANT
应用层连接时为何必须指定 database 参数而非靠 USE
很多 ORM 或连接池配置漏掉 database,导致连接建立后默认处于无库上下文。这时如果代码里写 "SELECT * FROM orders",MySQL 会报错 ERROR 1046 (3D000): No database selected;更糟的是,若某处误用了 USE tenant_b_prod,后续查询就全跑偏了。
正确姿势是:在连接串里固化库名,杜绝运行时切换。
- JDBC 示例:
jdbc:mysql://host:3306/?useSSL=false&database=tenant_a_prod - Python PyMySQL:
connect(database='tenant_a_prod') - 绝对禁止在 SQL 字符串中拼接库名,例如
"SELECT * FROM " + tenant_db + ".orders"—— 这是典型的 SQL 注入温床
为什么 information_schema 仍可能泄露租户信息
即使权限收得再紧,MySQL 8.0 默认允许普通用户访问 information_schema.TABLES 和 information_schema.COLUMNS,只要表名能被猜中(比如都叫 users),就可能通过 SELECT COUNT(*) FROM tenant_b_prod.users 推断出其他租户是否存在或活跃。
缓解手段有限但关键:
- 租户库名避免使用可预测前缀(如不用
tenant_1、tenant_2),改用哈希或 UUID,如tenant_x9f3a7b2 - 禁用
INFORMATION_SCHEMA中敏感视图的查询权限(需 MySQL 8.0.22+):REVOKE SELECT ON TABLE_SCHEMA.INFORMATION_SCHEMA.TABLES FROM 'tenant_a_app'@'%' - 应用日志中过滤掉含库名的 SQL,防止误打日志泄露租户拓扑
跨库隔离不是一劳永逸的事——权限只是第一道门,应用层的库名绑定、连接串固化、命名不可推测性,三者缺一不可。最容易被忽略的是:你以为 REVOKE SHOW DATABASES 就万事大吉,却忘了应用代码里一个 USE 或一个字符串拼接,就能让整套隔离形同虚设。











