虽然两者都能执行动态 SQL 字符串,但在安全性、性能和功能上存在本质区别。
通常强烈推荐使用 EXEC sp_executesql。
核心区别对比
| 特性 | EXEC sp_executesql |
EXEC(...) |
|---|---|---|
| 参数化支持 | 支持(核心优势) | 不支持(只能拼字符串) |
| SQL 注入风险 | 极低(通过参数隔离) | 极高(容易被恶意拼接) |
| 执行计划重用 | 高(参数变了,SQL 文本不变) | 低(参数变了,每次都要重新编译) |
| 输入/输出参数 | 支持 OUTPUT 参数返回值 |
无法直接返回变量值 |
| 变量作用域 | 无法直接访问外部变量 | 无法直接访问外部变量 |
关键差异详解
1. 安全性:防御 SQL 注入
EXEC(危险):必须把变量拼接到字符串里。如果变量包含恶意代码(如' ; DROP TABLE Users; --),会直接被执行。sp_executesql(安全):支持参数化查询。变量值只会被当做"数据"处理,绝对不会当做"代码"执行。
2. 性能:缓存执行计划
EXEC:每次传入不同的参数,生成的 SQL 字符串都不同(例如WHERE ID = 1和WHERE ID = 2)。SQL Server 必须每次都重新编译。sp_executesql:SQL 文本保持一致(例如WHERE ID = @id)。SQL Server 只编译一次,后续传入不同参数时直接重用执行计划,大幅提升性能。
3. 功能:支持 OUTPUT 参数
sp_executesql 可以把动态 SQL 执行的结果赋值回外部变量,而 EXEC 做不到。
-
sp_executesql示例:DECLARE @Count INT; -- 可以直接通过 @cnt OUTPUT 把值传出来 EXEC sp_executesql N'SELECT @cnt = COUNT(*) FROM Users WHERE Status = @status', N'@status INT, @cnt INT OUTPUT', @status = 1, @cnt = @Count OUTPUT;
唯一例外
当需要动态拼接数据库对象名称(如表名、列名)时,两者都无法使用参数化(因为表名不能作为参数)。此时必须使用字符串拼接,但使用 EXEC 的语法通常更简短:
-- 这种情况两者都需要注意防范注入,必须用 QUOTENAME()
DECLARE @TableName NVARCHAR(50) = 'Users';
EXEC('SELECT * FROM ' + @TableName);