grant语句必须写成grant ... ontenant_123. to ...,漏反引号或.将导致仅授usage权限,连show tables都被拒;租户库名含数字或短横线时更需反引号,否则语法报错。

GRANT语句必须带反引号和.*才生效
漏掉反引号或末尾的.*,权限就等于没授——这是线上最常踩的坑。MySQL解析tenant_123时会当成全局对象,而你没给过全局权限,结果连USE tenant_123都报错。
正确写法只有这一种:GRANT SELECT, INSERT, UPDATE ON `tenant_123`.* TO 'app1'@'10.20.30.%'。注意三点:
-
`tenant_123`:租户库名含数字、短横线(如tenant-abc)时,不加反引号直接语法报错 -
.*:缺了它,MySQL只给USAGE权限,连SHOW TABLES都不让执行 - 不能用
ON tenant_123或ON *.*替代:前者无效,后者越权
连接池必须强制初始化database上下文
连接复用时USE tenant_xyz残留是串库主因。ORM或连接池默认不设初始库,靠运行时切库极不可靠。
不同连接池处理方式差异大:
- JDBC连接串要显式加
database=tenant_xyz参数,但旧版mysql-connector-java重连后大概率丢失该设置 - Druid配
connectionInitSqls=["USE tenant_xyz"]不够,还得在每次获取连接后校验SELECT DATABASE()返回值是否匹配 - HikariCP无内置init SQL,必须用
ConnectionCustomizer.onAcquire()执行USE tenant_xyz,且每次acquire都重置
SHOW DATABASES无法真正隐藏其他租户库
这不是权限配置错误,而是MySQL设计如此:只要账号有任意库权限,SHOW DATABASES就列出全部库名。5.7及更早版本完全无法过滤;8.0+需配合information_schema_restricted=ON + 细粒度GRANT SELECT ON information_schema.*才能有限收敛。
真正该做的只有两件事:
- 执行
REVOKE SHOW DATABASES ON *.* FROM 'tenant_app'@'%'(心理防线,非技术必需) - 禁止应用层把
SHOW DATABASES结果暴露给租户——它本就不该出现在前端、日志或管理后台
租户建库建号必须由专用服务统一执行
绝不能把CREATE DATABASE或CREATE USER权限直接给业务账号。一旦放开,租户就能建mysql、information_schema甚至tenant_999这种撞名库,直接破坏隔离边界。
手动往mysql.db表插数据更危险:权限缓存不刷新、结构变更兼容性差,常出现SHOW GRANTS查不到刚授的权限。
安全做法是:
- 运维脚本或平台服务统一创建租户库和账号
- 库名强制带前缀,如
tenant_a_prod、tenant_b_staging - 账号命名与库名强关联,避免
user123这类泛化名称
租户隔离真正的复杂点不在SQL怎么写,而在权限上下文如何贯穿整个请求生命周期——从连接建立、查询执行到连接归还,任何一环脱钩都可能让tenant_id失效。











