动态sql是在运行时生成并执行的sql语句,用于应对字段、表名、条件、排序等不确定场景;sql server推荐用sp_executesql参数化执行,mysql用prepare/execute,postgresql用execute…using,均需防范sql注入。

动态 SQL 是在运行时才生成并执行的 SQL 语句,它让存储过程具备更强的灵活性,能根据输入参数、业务规则或元数据构造不同的查询逻辑。在 SQL Server、MySQL、PostgreSQL 等主流数据库中,都支持动态 SQL,但语法和安全机制略有差异。
为什么在存储过程中用动态 SQL?
静态 SQL 在编写时就确定了表名、字段、条件等结构,而实际业务中常遇到以下场景:
- 用户可自选查询字段(如“只查姓名和电话”或“查全部字段”)
- 表名需由参数传入(如按月份分表的日志查询:log_202401、log_202402)
- WHERE 条件数量不固定(如多选筛选项:地区、状态、时间范围可任意组合)
- 需要拼接不同排序字段或方向(ORDER BY @sortColumn @sortOrder)
SQL Server 中 EXEC 和 sp_executesql 的区别
SQL Server 提供两种执行动态 SQL 的方式,推荐优先使用 sp_executesql:
- EXEC(@sql):简单直接,但无法复用执行计划,且容易受 SQL 注入影响;参数只能靠字符串拼接
- sp_executesql @sql, @params, @param1, @param2...:支持参数化,执行计划可缓存,更安全高效
示例(安全写法):
DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM Users WHERE Status = @status'; DECLARE @params NVARCHAR(100) = '@status INT'; EXEC sp_executesql @sql, @params, @status = 1;
如何避免 SQL 注入风险?
动态拼接时若直接把用户输入嵌入 SQL 字符串,极易被注入。关键原则是:
- 所有 值类参数 必须用 sp_executesql 参数化传递(如 WHERE name = @name)
- 所有 对象名(表名、列名、排序字段) 不能直接拼接,应先校验合法性:
• 使用 QUOTENAME() 包裹(SQL Server),确保转义特殊字符
• 或查系统视图(如 sys.tables、INFORMATION_SCHEMA.COLUMNS)确认存在 - 避免拼接 ORDER BY 后的字段名和方向,可用 CASE 预定义合法选项
错误示范(危险):
SET @sql = 'SELECT * FROM ' + @tableName + ' WHERE id = ' + @id;
正确做法(SQL Server):
SET @sql = 'SELECT * FROM ' + QUOTENAME(@tableName) + ' WHERE id = @id'; EXEC sp_executesql @sql, N'@id INT', @id = @id;
MySQL 和 PostgreSQL 的对应实现
MySQL 使用 PREPARE + EXECUTE,同样需注意参数化:
SET @sql = CONCAT('SELECT * FROM ', @table_name, ' WHERE status = ?');
PREPARE stmt FROM @sql;
EXECUTE stmt USING @status;
DEALLOCATE PREPARE stmt;PostgreSQL 推荐用 EXECUTE ... USING(在 plpgsql 函数中):
EXECUTE format('SELECT * FROM %I WHERE status = $1', table_name)
USING status_value;其中 %I 自动加双引号并转义标识符,$1 绑定参数,兼顾安全与可读性。










