SQL 动态 SQL 执行与调试

10次阅读

动态 sql 应优先使用 sp_executesql 实现参数化以复用执行计划并防注入;对象名(表名 / 列名)须经 quotename() 处理,值必须用参数传递,拼接后需 print 或 select 输出验证逻辑正确性。

SQL 动态 SQL 执行与调试

动态 SQL 执行前必须检查 EXECsp_executesql 的语义差异

直接拼字符串后用 EXEC 执行,看似简单,但参数无法安全传入、执行计划不复用、容易被注入。真正该用的是 sp_executesql——它支持参数化,SQL Server 能缓存执行计划,也避免引号嵌套灾难。

  • EXEC('SELECT * FROM' + @table_name):危险,@table_name 一旦含单引号或恶意片段就崩
  • sp_executesql N'SELECT * FROM' + @table_name, N'@id INT', @id = 123:错!表名不能参数化,只能用字符串拼接;但 WHERE 条件里的值必须参数化
  • 正确姿势是:表名 / 列名等对象名用白名单校验或 QUOTENAME() 处理,WHERE/ORDER BY 中的值一律走 sp_executesql 参数

调试动态 SQL 时别只看“执行成功”,要先查生成的语句本身

很多问题不是语法错,而是拼出来的 SQL 逻辑不对——比如多了一个逗号、少了一个括号、AND 被写成 OR,或者时间范围反了。光看“命令已成功完成”没用。

  • 把拼好的 SQL 字符串先赋给变量,用 PRINT @sqlSELECT @sql 输出,复制到新窗口手动执行验证
  • 注意:SQL Server Management Studio 默认截断 PRINT 输出为 4000 字符,长语句要用 SELECT CAST(@sql AS XML) 避免截断
  • 如果用了 sp_executesql,记得把参数也一起打印出来,比如 SELECT @sql, @params, @param_values,不然光看 SQL 不知道实际代入了啥

拼接动态 SQL 时 QUOTENAME() 不是可选项,是必选项

用户输入的表名、列名、排序字段,哪怕看起来“很干净”,也不能裸拼。SQL Server 对标识符有严格规则(比如含空格、中划线、中文),不处理会直接报错:Incorrect syntax near '-'.

  • QUOTENAME(@table_name) 会自动加 [] 并转义内部的 ],比手写 '[' + @table_name + ']' 安全得多
  • 不要对值内容用 QUOTENAME()——那是给对象名用的;值要用参数,而不是拼进字符串
  • 如果允许用户选多个列,用 STRING_AGG(QUOTENAME(col), ',')(SQL Server 2017+)或循环拼接,别忘了每个列都套一层

动态 SQL 在存储过程中性能差?先确认是否真用了执行计划缓存

有人说“动态 SQL 性能差”,其实是没分清场景:纯字符串拼接每次都是新语句,肯定不缓存;但用 sp_executesql 且参数类型一致、SQL 文本完全相同,就能复用计划——和静态 SQL 一样快。

  • 检查是否命中缓存:查 sys.dm_exec_query_stats + sys.dm_exec_sql_text,看同一段动态 SQL 的 execution_count 是否递增
  • 常见破环缓存的行为:在 SQL 字符串里硬编码不同时间戳、用 GETDATE()、拼入不同长度的字符串(比如 'abc' vs 'abcd')、参数类型不一致(@id INT vs @id BIGINT
  • 如果必须拼时间范围,用参数代替,比如 N'WHERE created_at BETWEEN @start AND @end',而不是 'WHERE created_at BETWEEN''' + CONVERT(VARCHAR, @start) + '''AND ……'

动态 SQL 最难的从来不是怎么写出来,而是怎么让拼出来的那条语句,在所有边界输入下都合法、安全、可预测。尤其当它混着用户可控字段、条件开关、分页参数一起上时,漏掉一个 QUOTENAME() 或参数类型不匹配,就可能在线上突然报错或查出错误数据。

text=ZqhQzanResources