什么是带参数的 SQL 查询?
带参数的 SQL 查询(也常被称为 参数化查询 或 预处理语句)是一种将 SQL 语句的“结构”和“数据”分开处理的技术。

(图片来源网络,侵删)
你先定义好 SQL 语句的“骨架”(SELECT * FROM users WHERE name = ?),然后将需要查询或修改的具体数据('张三')作为“参数”传递进去。
打个比方: 这就像填空题,SQL 语句的模板是题目,参数是你需要填写的答案。
为什么使用参数化查询?(核心优势)
这是最重要的一点,理解了它,你就知道为什么必须使用参数化查询。
防止 SQL 注入攻击
这是最关键、最核心的原因,SQL 注入是一种恶意攻击,攻击者通过在输入字段中插入恶意的 SQL 代码,来篡改你的后台 SQL 查询。

(图片来源网络,侵删)
不安全的示例(字符串拼接):
假设你的代码是这样写的:
# 伪代码,用户输入 user_input = "admin' -- " # 危险的 SQL 拼接 sql = "SELECT * FROM users WHERE username = '" + user_input + "' AND password = 'somepassword'"
最终执行的 SQL 会变成:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'somepassword'
在 SQL 中, 是注释符,上面的查询会忽略掉 后面的所有内容,这意味着系统只会验证用户名 admin,而完全绕过了密码检查!这会造成严重的安全漏洞。

(图片来源网络,侵删)
安全的示例(参数化查询):
使用参数化查询后,上面的代码会变成这样:
# 伪代码 user_input = "admin' -- " # 安全的参数化查询 sql = "SELECT * FROM users WHERE username = ? AND password = ?" # 参数会被数据库驱动安全地处理,当作纯数据,而不是 SQL 代码的一部分 params = (user_input, "somepassword")
数据库引擎会确保 user_input 的值被严格当作一个字符串来处理,即使它包含 或 等特殊字符,也绝不会影响 SQL 语句的结构。
提高性能和可读性
- 性能: 对于需要频繁执行的 SQL 语句(一个网站的搜索功能),数据库可以缓存编译后的 SQL 语句(执行计划),当你使用参数化查询时,数据库只需编译一次模板,后续只需传入不同的参数即可重用这个编译好的计划,大大减少了编译时间,提升了性能。
- 可读性: 代码更清晰,SQL 模板和输入数据是分离的,代码更容易阅读和维护。
参数定义和使用方式
参数的定义和使用方式因编程语言和数据库驱动而异,但其核心思想是相同的:使用占位符。
下面我们通过几种主流的编程语言和数据库来举例说明。
占位符的类型
不同的数据库和驱动使用不同的占位符符号:
- (问号): 最常见的占位符,用于 JDBC (Java), ODBC, Python (sqlite3, pyodbc) 等。
@name(命名参数): 使用一个名称作为占位符,更具可读性,用于 .NET (SqlCommand), Python (psycopg2) 等。name(命名参数): 类似@name,用于 Python (sqlite3, SQLAlchemy ORM), Ruby, Oracle 等。$1,$2(位置参数): 使用数字表示参数的位置,用于 PostgreSQL (libpq 驱动), Go 等。
编程语言示例
示例1:Python (使用 sqlite3)
sqlite3 使用 作为占位符。
import sqlite3
# 1. 连接到数据库
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
# 2. 定义带参数的 SQL 查询语句模板
# ? 是一个占位符
sql_query = "SELECT * FROM users WHERE age > ? AND department = ?"
# 3. 定义要传入的参数(元组或列表)
params = (25, '技术部') # 对应 ? 的顺序
# 4. 执行查询,传入参数
# 注意:参数是作为第二个参数传递的
cursor.execute(sql_query, params)
# 5. 获取查询结果
results = cursor.fetchall()
for row in results:
print(row)
# 6. 关闭连接
conn.close()
示例2:Python (使用 psycopg2 for PostgreSQL)
psycopg2 使用 %s 作为占位符(注意:不是字符串格式化的 %s,而是它自己定义的)。
import psycopg2
# 1. 连接到数据库
conn = psycopg2.connect("dbname=test user=postgres")
cursor = conn.cursor()
# 2. 定义带参数的 SQL 查询语句模板
# %s 是占位符
sql_query = "SELECT * FROM products WHERE price > %s AND category = %s"
# 3. 定义要传入的参数(元组)
params = (100, '电子产品')
# 4. 执行查询
cursor.execute(sql_query, params)
# 5. 获取结果
results = cursor.fetchall()
for row in results:
print(row)
# 6. 关闭连接
conn.close()
示例3:Java (使用 JDBC)
JDBC 使用 作为占位符。
import java.sql.*;
public class ParameterizedQueryExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mydatabase";
String user = "username";
String password = "password";
// 1. 定义带参数的 SQL 查询语句模板
String sql = "SELECT * FROM employees WHERE department = ? AND hire_date > ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 2. 设置参数 (索引从 1 开始)
pstmt.setString(1, "销售部"); // 设置第一个 ? 为字符串
pstmt.setDate(2, Date.valueOf("2025-01-01")); // 设置第二个 ? 为日期
// 3. 执行查询
ResultSet rs = pstmt.executeQuery();
// 4. 处理结果集
while (rs.next()) {
// ... 获取数据
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
示例4:C# (使用 ADO.NET)
C# 常用 @param 的命名参数形式,可读性更高。
using System;
using System.Data;
using System.Data.SqlClient;
public class DataExample
{
public void GetUsers(string department)
{
string connectionString = "Your_Connection_String";
string sql = "SELECT * FROM Users WHERE Department = @DeptName AND IsActive = @IsActive";
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(sql, connection))
{
// 1. 定义并添加参数
command.Parameters.Add(new SqlParameter("@DeptName", SqlDbType.VarChar, 50));
command.Parameters["@DeptName"].Value = department;
command.Parameters.Add(new SqlParameter("@IsActive", SqlDbType.Bit));
command.Parameters["@IsActive"].Value = true;
try
{
connection.Open();
// 2. 执行查询
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
// ... 读取数据
}
}
}
catch (Exception ex)
{
// 处理异常
}
}
}
}
| 特性 | 描述 |
|---|---|
| 核心目的 | 将 SQL 逻辑与数据分离,提高安全性和性能。 |
| 主要优势 | 防止 SQL 注入攻击 (最重要) 提升数据库查询性能 (通过预编译和缓存) 代码更清晰、更易维护 |
| 工作原理 | 定义一个包含占位符的 SQL 模板。 2. 创建一个预处理语句对象,并将模板发送给数据库进行预编译。 3. 将参数值安全地绑定到占位符上。 4. 执行预处理语句。 |
| 占位符 | (JDBC, ODBC), @name (C#), name (Python, Oracle), $1 (PostgreSQL) |
| 最佳实践 | 永远不要通过字符串拼接来构建 SQL 查询,对于任何来自用户输入、配置文件或不可信来源的数据,始终使用参数化查询。 |
掌握参数化查询是每个后端开发者的必备技能,它不仅是编写高质量代码的体现,更是保障应用程序安全的基石。
