SQL 动态 SQL 在存储过程中的应用

1次阅读

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

SQL 动态 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 绑定参数,兼顾安全与可读性。

text=ZqhQzanResources