数据库名不能参数化,因其属语法结构而非值,use @db_name等写法直接报错;必须通过白名单+系统视图双重校验库存在性与安全性,并用quotename()安全拼接,优先采用连接字符串路由替代sql层动态切库。

数据库名不能用参数化 SQL,因为它是 SQL 语法结构的一部分,不是“值”——所有主流数据库(SQL Server、MySQL、Oracle)都不允许 @db_name 或 ? 出现在 USE、FROM 或三段式标识符的最左段中,强行使用会直接报错。
为什么 USE @db_name 会失败
数据库名属于“对象作用域标识符”,必须在语句编译前就确定。SQL Server 报 Incorrect syntax near '@',MySQL 报 You have an error in your SQL syntax,Oracle 报 ORA-00922: missing or invalid option。这不是驱动或 ORM 的限制,是数据库引擎的硬性解析规则。
-
USE ?、SELECT * FROM @db.@schema.@table这类写法全部非法,连语法检查都过不了 - 试图用
sp_executesql绑定@db_name到USE @db_name中,结果只是把变量当字符串字面量处理,不会切换上下文 - 哪怕你用
QUOTENAME(@db_name)拼进字符串,也只解决单引号闭合问题,不防mydb; DROP DATABASE otherdb--这类多语句注入
动态库名必须走白名单 + 系统视图双重校验
不能靠过滤字符或转义兜底,必须确认该库名真实存在、归属预期安全范围,并且业务逻辑允许访问。
- 先查
sys.databases(SQL Server)或information_schema.SCHEMATA(MySQL)确认库存在且状态正常(state = 0) - 显式限定可选范围:
IF @db_name NOT IN ('tenant_a', 'tenant_b', 'reporting'),不在列表内直接THROW - 若支持 schema 级隔离,必须拆开校验:
IF @schema NOT IN ('dbo', 'shared') AND NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = @schema) - 拼接时严格用
QUOTENAME(@db_name),而不是'[' + @db_name + ']'—— 后者对嵌套括号、Unicode 控制符无防护力
更安全的替代方案:连接字符串路由,而非 SQL 层切换
在应用层根据租户 ID 或上下文选择对应数据库连接,比在 SQL 内动态切库更可控、更易审计。
- 避免在存储过程中做
USE [db]切换,改由应用代码决定SqlConnection或DataSource实例 - 如果必须复用同一连接,优先用三段式写法:
SELECT * FROM [tenant_a].dbo.users,其中[tenant_a]来自白名单校验后的QUOTENAME()结果 - 禁用
EXEC(@sql)执行含USE的语句——它绕过所有参数化机制,等于放弃防御底线 - 所有动态库名输入,无论来自 API、配置项还是日志字段,只要后续参与 SQL 构建,就必须重新校验;不能假设“之前读过一次就可信”
真正难的不是写对一个 QUOTENAME(),而是确保每个拼接点之前都有白名单判断,且校验逻辑不被缓存、不被跳过、不依赖外部数据源——哪怕只漏掉一个 IF EXISTS 检查,整个动态库名路径就退化成高危入口。











