grant必须写成grant select on tenant_x.,因为mysql权限系统要求库名用反引号包裹(防短横线、数字开头等非法标识符),且末尾.不可省略(否则仅授usage权限,连show tables都被拒);漏掉任一要素即权限不生效。

GRANT 必须写成 ON <code>tenant_x.,漏掉反引号或 . 就等于没授权——这是权限失效最常见、最隐蔽的根源。
GRANT ON tenant_x.* 为什么必须带反引号和 .*?
MySQL 权限系统不认裸名 tenant_x:它会被解析为“全局权限对象”,而你没授过全局权限,结果就是连 USE tenant_x 都被拒绝。
必须写成 GRANT SELECT ON `tenant_x`.* TO 'u1'@'10.20.30.%',原因有三:
-
`tenant_x`:租户库名含数字、短横线(如`tenant-123`)时,不加反引号直接语法报错 -
.*:权限粒度绑定到“库下所有表”,漏掉就只拿到 USAGE(连SHOW TABLES都不许) - 不能用
ON *.*或ON tenant_x替代——前者越权,后者无效
为什么 SHOW DATABASES 还能看到别的租户库?
这不是权限没设好,是 MySQL 的设计行为:只要账号存在且有任意库权限,SHOW DATABASES 就列出全部库名。5.7 及更早版本无法真正隐藏,8.0+ 也需配合 information_schema_restricted=ON + 细粒度 GRANT SELECT ON information_schema.* 才能有限过滤。
真正该做的只有两件事:
- 执行
REVOKE SHOW DATABASES ON *.* FROM 'tenant_x_app'@'10.20.30.%'(心理防线,非技术必需) - 禁止应用层把
SHOW DATABASES结果暴露给租户——它本就不该出现在前端或日志里
连接池里怎么防止 USE tenant_y 残留导致串库?
USE tenant_x 是会话级命令,连接复用时极易残留旧上下文。ORM 或连接池默认不设初始库,靠运行时 USE 切换,风险极高。
可靠做法是强制在连接串里指定 database 参数:
- JDBC:
jdbc:mysql://host:3306/?useSSL=false&database=tenant_x(注意:旧版mysql-connector-java的?database=在重连时大概率丢失) - Druid:配
connectionInitSqls=["USE tenant_x"]不够,得额外校验SELECT DATABASE()返回值是否匹配 - HikariCP:无内置 init SQL,必须用
ConnectionCustomizer.onAcquire()执行USE tenant_x,且每次获取连接都重置
建库建号为什么必须由专用服务统一执行?
租户账号一旦拿到 CREATE DATABASE 权限,就能建任意库名(包括 mysql、information_schema),直接撞进别人地盘;手动往 mysql.db 表插数据更危险——权限缓存不刷新、结构变更兼容性差,常出现 SHOW GRANTS 查不到但实际没权限的诡异状态。
自动化脚本或专用服务应做到:
- 建库时强制指定
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci - 建号后立刻
GRANT ... ON `tenant_x`.*,再REVOKE ALL PRIVILEGES ON *.* - 白名单校验库名格式(如只允许
tenant_\d+),禁用租户自建库
最易被忽略的点:权限隔离不是“建完就完事”,而是贯穿建库、授权、连接、查询全链路——任何一个环节松动,比如忘了收 SHOW DATABASES,或 ORM 默认连接没绑 database,都会让前面所有配置形同虚设。











