当然,你可能已经使用过绑定参数了,但你是否知道它是一种性能优化技巧呢?
你可能听说过类似的说法:“使用绑定参数(参数化查询)而不是拼接 SQL 字符串可以提升 SQL 性能。”
接下来,你将了解其中的原因。
什么是绑定参数?
绑定参数是 SQL 语句中的一个占位符,是一个变量,其值在执行时提供。
占位符可以是:
-
? -
@variable -
:variable
因此,与其在查询中硬编码字面值,不如“绑定”一个参数:
SELECT
*
FROM [db_sales_test].[dbo].[fact_sales]
WHERE [product_id] = 60;
EXEC sp_executesql
N'SELECT * FROM fact_sales WHERE product_id = @product_id',
N'@product_id INT',
@product_id = 60;
这两条查询会返回相同的数据,但 SQL Server 在内部对它们的处理方式完全不同。
不使用参数会发生什么?
当你运行以下查询时:
SELECT
*
FROM [db_sales_test].[dbo].[fact_sales]
WHERE [product_id] = 60;
SQL Server 必须执行以下操作:
-
解析 SQL 字符串。 -
验证元数据(表、列、索引)。 -
运行优化器以选择最佳计划。 -
缓存该执行计划。
如果你稍后运行以下查询:
SELECT
*
FROM [db_sales_test].[dbo].[fact_sales]
WHERE [product_id] = 147;
SQL Server 会将这条查询视为全新的查询,因为只是值不同。因此,它会编译并缓存第二个执行计划,尽管查询的其余部分完全相同。
听起来很疯狂,对吧?想象一下这发生在事务型数据库中……
突然间,你的计划缓存中充满了几乎相同的查询,浪费了 CPU 和内存。
绑定参数登场
如果我们对查询进行参数化:
EXEC sp_executesql
N'SELECT * FROM fact_sales WHERE product_id = @product_id',
N'@product_id INT',
@product_id = 60;
-
查询文本将始终保持不变。 -
只有参数值会改变。 -
SQL Server 将重用相同的执行计划(我们稍后会讨论这一点)。
这意味着你将减少编译开销,计划缓存也会更小,从而实现快速查询。
实际操作
让我们通过一个示例来验证这一点,好吗?
首先运行以下两条查询:
SELECT
*
FROM [db_sales_test].[dbo].[fact_sales]
WHERE [product_id] = 147;
SELECT
*
FROM [db_sales_test].[dbo].[fact_sales]
WHERE [product_id] = 60;
然后,我们可以检查缓存的计划:
SELECT
cp.usecounts,
cp.objtype,
st.text
FROM sys.dm_exec_cached_plans cp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
WHERE st.text LIKE '%fact_sales%';
在结果中,我们可以看到两个独立的缓存计划:
现在,如果我们运行一条参数化的查询,使用 sp_executesql:
EXEC sp_executesql
N'SELECT * FROM fact_sales WHERE product_id = @product_id',
N'@product_id INT',
@product_id = 58;
我们可以看到只有一个计划,随着查询的重复使用,usecounts 会增加。
不错吧! :)
重要提示:另一个好处是绑定参数可以防止 SQL 注入,因此在应用程序中使用它是一种很好的实践。
但是……参数嗅探怎么办?
是的,世界并非尽善尽美。我们在使用绑定参数时还需要考虑一些问题……
有一个问题是:参数化可能会导致 参数嗅探问题。
例如:
-
假设 [product_id] = 1只返回 3 行,但[product_id] = 500返回 100 万行。
如果 SQL Server 使用 [product_id] = 1 来编译计划,它可能会选择嵌套循环连接(适用于小数据集)。
但当它尝试运行 [product_id] = 500 时,相同的计划可能会表现得很糟糕。
这就是 参数嗅探,SQL Server 会使用第一个参数来构建计划,而这个计划在后续执行中可能表现不佳。
一个解决方案是使用 OPTION (RECOMPILE):
EXEC sp_executesql
N'SELECT * FROM fact_sales WHERE product_id = @product_id OPTION (RECOMPILE);',
N'@product_id INT',
@product_id = 2;
SQL 将创建另一个缓存执行计划:
使用绑定参数不仅仅是写出“更干净”的 SQL。
它关乎:
-
执行计划重用 → 更快的查询。 -
减少浪费的 CPU → 更高的吞吐量。 -
更小的计划缓存 → 更少的内存压力。 -
更安全的查询 → 防止 SQL 注入。
参数化是你能够实现的最简单且最有效的性能优化之一。

