参数化查询
本文档详细介绍 cDataBase 类的参数化查询功能,这是防止 SQL 注入攻击的重要安全特性。
? 目录
参数化查询概述
什么是参数化查询
参数化查询是将 SQL 语句和参数值分开处理的技术,参数值通过占位符(通常是 ?)传递。
为什么使用参数化查询
- 防止 SQL 注入 - 参数值会被转义,无法执行恶意 SQL 代码
- 性能优化 - 数据库可以缓存执行计划
- 类型安全 - 自动处理数据类型转换
- 代码清晰 - SQL 语句和参数值分离,易于维护
SQL 注入示例
vb
' ? 危险:直接拼接 SQL(容易 SQL 注入)
Dim sName As String
sName = "'; DROP TABLE users; --"
db.Sql("SELECT * FROM users WHERE name = '" & sName & "'").Query
' 实际执行的 SQL: SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
' 结果:users 表被删除!
' ? 安全:使用参数化查询
db.Sql("SELECT * FROM users WHERE name = ?") _
.Param("name", sName, adVarWChar) _
.QueryParam
' 参数值会被安全处理,无法执行恶意代码Param 方法
语法
vb
Function Param(ByVal ParamName As String, _
ByVal ParamValue As Variant, _
Optional ByVal ParamType As DataTypeEnum = adVarChar) As cDataBase参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
ParamName | String | 参数名称(用于标识,实际使用 ? 占位符) |
ParamValue | Variant | 参数值(必需) |
ParamType | DataTypeEnum | 参数数据类型(可选,默认 adVarChar) |
返回值
返回 cDataBase 对象,支持链式调用。
示例
vb
' 添加单个参数
db.Sql("SELECT * FROM users WHERE id = ?") _
.Param("id", 1, adInteger)
' 添加多个参数(链式调用)
db.Sql("SELECT * FROM users WHERE name = ? AND age > ?") _
.Param("name", "张三", adVarWChar) _
.Param("age", 18, adInteger)ExecParam 方法
语法
ExecParam 方法执行带参数的 INSERT、UPDATE、DELETE 操作。
vb
Function ExecParam(Optional RecordsAffected) As Boolean参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
RecordsAffected | Variant | 返回受影响的行数(可选) |
返回值
True- 执行成功False- 执行失败(可通过LastErr查看错误信息)
示例
vb
' 插入数据
If db.Sql("INSERT INTO users (name, age, email) VALUES (?, ?, ?)") _
.Param("name", "张三", adVarWChar) _
.Param("age", 25, adInteger) _
.Param("email", "zhangsan@example.com", adVarWChar) _
.ExecParam Then
Debug.Print "插入成功,ID: " & db.LastInsertId
End If
' 更新数据
If db.Sql("UPDATE users SET age = ?, email = ? WHERE id = ?") _
.Param("age", 26, adInteger) _
.Param("email", "newemail@example.com", adVarWChar) _
.Param("id", 1, adInteger) _
.ExecParam Then
Debug.Print "更新成功"
End If
' 删除数据
If db.Sql("DELETE FROM users WHERE id = ?") _
.Param("id", 1, adInteger) _
.ExecParam Then
Debug.Print "删除成功"
End IfQueryParam 方法
语法
QueryParam 方法执行带参数的 SELECT 查询,结果存储在 db.Rs 中。
vb
Function QueryParam(Optional CurType As CursorTypeEnum = adOpenKeyset, _
Optional LockType As LockTypeEnum = adLockOptimistic) As Boolean参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
CurType | CursorTypeEnum | 游标类型(可选,默认 adOpenKeyset) |
LockType | LockTypeEnum | 锁定类型(可选,默认 adLockOptimistic) |
返回值
True- 查询成功,结果存储在db.Rs中False- 查询失败(可通过LastErr查看错误信息)
重要:QueryParam 方法返回的是布尔值,不是 Recordset。查询结果需要通过 db.Rs 属性访问。
示例
vb
' 查询单条记录(使用 db.Rs 访问结果)
If db.Sql("SELECT * FROM users WHERE id = ?") _
.Param("id", 1, adInteger) _
.QueryParam Then
' 使用 db.Rs 访问 Recordset
If Not db.Rs.EOF Then
Debug.Print db.Rs("name")
End If
db.Rs.Close
End IfFetchParam 方法
语法
FetchParam 方法执行带参数的 SELECT 查询,并自动将结果转换为 db.Rows(cCollection)和 db.Row(Dictionary)。这是参数化查询的主要场景,配合 JSON 输出使用。
vb
Function FetchParam(Optional CurType As CursorTypeEnum = adOpenKeyset, _
Optional LockType As LockTypeEnum = adLockOptimistic) As Boolean参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
CurType | CursorTypeEnum | 游标类型(可选,默认 adOpenKeyset) |
LockType | LockTypeEnum | 锁定类型(可选,默认 adLockOptimistic) |
返回值
True- 查询成功,结果自动转换为db.Rows和db.RowFalse- 查询失败(可通过LastErr查看错误信息)
功能
- 内部先调用
QueryParam执行参数化查询 - 然后将
db.Rs自动转换为db.Rows(cCollection)和db.Row(Dictionary) - 可直接用于 JSON 序列化,配合
cJson和cHttpServerResponse使用 - 执行完毕后自动清理参数(调用
ClearParam)
示例
vb
' 参数化查询 + 自动转换为 Rows/Row(推荐方式)
If db.Sql("SELECT * FROM users WHERE age > ? AND status = ?") _
.Param("age", 18, adInteger) _
.Param("status", "active", adVarWChar) _
.FetchParam Then
' 直接访问 Rows 集合
Dim i As Long
For i = 1 To db.Rows.Count
Debug.Print db.Rows(i)("name")
Next
' 或直接用于 JSON 输出
ctx.Response.Json db.Rows
End If
' 查询单条记录
If db.Sql("SELECT * FROM users WHERE id = ?") _
.Param("id", 1, adInteger) _
.FetchParam Then
If db.Rows.Count > 0 Then
Debug.Print db.Row("name")
End If
End IfClearParam 方法
语法
ClearParam 方法清理参数化查询的参数和 Command 对象。
vb
Sub ClearParam()功能
- 清除所有已添加的参数
- 释放 Command 对象
ExecParam、QueryParam、FetchParam执行后会自动调用ClearParam
使用场景
vb
' 场景 1:手动清理(只调用了 Param 但未执行)
db.Sql("SELECT * FROM users WHERE id = ?").Param("id", 1, adInteger)
' 决定不执行此查询,需要手动清理
db.ClearParam
' 场景 2:正常执行后无需手动清理(自动完成)
If db.Sql("INSERT INTO users (name) VALUES (?)") _
.Param("name", "张三", adVarWChar) _
.ExecParam Then
' ExecParam 已自动清理参数,无需手动调用 ClearParam
End If数据类型
AdoDataType 枚举
cDataBase 类定义了 AdoDataType 枚举,引用 VBMAN 后这些枚举值全局可用,无需 VBMAN. 前缀(直接写 adVarWChar 即可)。这些值与 ADO 的 DataTypeEnum 完全一致。
完整枚举定义
vb
Public Enum AdoDataType
adEmpty = 0 ' 空
adTinyInt = 16 ' 8 位有符号整数
adSmallInt = 2 ' 16 位有符号整数
adInteger = 3 ' 32 位有符号整数
adBigInt = 20 ' 64 位有符号整数
adUnsignedTinyInt = 17 ' 8 位无符号整数
adUnsignedSmallInt = 18 ' 16 位无符号整数
adUnsignedInt = 19 ' 32 位无符号整数
adUnsignedBigInt = 21 ' 64 位无符号整数
adSingle = 4 ' 单精度浮点数
adDouble = 5 ' 双精度浮点数
adCurrency = 6 ' 货币类型
adDecimal = 14 ' 精确数值
adBoolean = 11 ' 布尔值
adDate = 7 ' 日期时间
adDBDate = 133 ' 日期(数据库格式)
adDBTime = 134 ' 时间(数据库格式)
adDBTimeStamp = 135 ' 时间戳(数据库格式)
adVarChar = 200 ' ANSI 可变长度字符串
adLongVarChar = 201 ' ANSI 长可变长度字符串
adVarWChar = 202 ' Unicode 可变长度字符串
adLongVarWChar = 203 ' Unicode 长可变长度字符串
adBinary = 128 ' 二进制数据
adVarBinary = 204 ' 可变长度二进制数据
adLongVarBinary = 205 ' 长可变长度二进制数据
adGUID = 72 ' GUID
End Enum常用数据类型
| 类型 | 值 | 说明 | 示例 |
|---|---|---|---|
adVarChar | 200 | 可变长度字符串 | "张三" |
adVarWChar | 202 | Unicode 字符串 | "张三" |
adInteger | 3 | 32 位整数 | 25 |
adBigInt | 20 | 64 位整数 | 1234567890 |
adDouble | 5 | 双精度浮点数 | 3.14 |
adDate | 7 | 日期时间 | Now |
adBoolean | 11 | 布尔值 | True |
adDecimal | 14 | 精确数值 | 99.99 |
数据类型选择
vb
' 字符串
db.Param("name", "张三", adVarWChar) ' Unicode 字符串(推荐)
db.Param("name", "张三", adVarChar) ' ANSI 字符串
' 整数
db.Param("age", 25, adInteger) ' 32 位整数
db.Param("id", 1234567890, adBigInt) ' 64 位整数
' 浮点数
db.Param("price", 99.99, adDouble) ' 双精度
db.Param("amount", 99.99, adDecimal) ' 精确数值
' 日期时间
db.Param("created", Now, adDate) ' 日期时间
' 布尔值
db.Param("active", True, adBoolean) ' 布尔值安全优势
SQL 注入防护
vb
' ? 危险:直接拼接
Dim sInput As String
sInput = "'; DROP TABLE users; --"
db.Sql("SELECT * FROM users WHERE name = '" & sInput & "'").Query
' 结果:users 表被删除
' ? 安全:参数化查询
db.Sql("SELECT * FROM users WHERE name = ?") _
.Param("name", sInput, adVarWChar) _
.QueryParam
' 参数值被安全处理,无法执行恶意代码特殊字符处理
vb
' ? 危险:特殊字符可能导致错误
Dim sName As String
sName = "O'Brien"
db.Sql("SELECT * FROM users WHERE name = '" & sName & "'").Query
' SQL: SELECT * FROM users WHERE name = 'O'Brien'
' 错误:单引号未转义
' ? 安全:参数化查询自动处理
db.Sql("SELECT * FROM users WHERE name = ?") _
.Param("name", sName, adVarWChar) _
.QueryParam
' 特殊字符被自动转义使用示例
示例 1:用户登录
vb
Function UserLogin(sUsername As String, sPassword As String) As Boolean
' 使用参数化查询防止 SQL 注入
If db.Sql("SELECT * FROM users WHERE username = ? AND password = ?") _
.Param("username", sUsername, adVarWChar) _
.Param("password", sPassword, adVarWChar) _
.QueryParam Then
If db.Rows.Count > 0 Then
UserLogin = True
Else
UserLogin = False
End If
Else
UserLogin = False
End If
End Function示例 2:搜索功能
vb
Function SearchUsers(sKeyword As String) As Collection
Set SearchUsers = New Collection
' 使用 LIKE 和参数化查询
Dim sSql As String
sSql = "SELECT * FROM users WHERE name LIKE ? OR email LIKE ?"
If db.Sql(sSql) _
.Param("name", "%" & sKeyword & "%", adVarWChar) _
.Param("email", "%" & sKeyword & "%", adVarWChar) _
.QueryParam Then
Set SearchUsers = db.Rows
End If
End Function示例 3:批量插入
vb
Sub BatchInsertUsers(colUsers As Collection)
db.TransBegin
Dim i As Long
For i = 1 To colUsers.Count
Dim dictUser As Dictionary
Set dictUser = colUsers(i)
If Not db.Sql("INSERT INTO users (name, age, email) VALUES (?, ?, ?)") _
.Param("name", dictUser("name"), adVarWChar) _
.Param("age", dictUser("age"), adInteger) _
.Param("email", dictUser("email"), adVarWChar) _
.ExecParam Then
db.TransRollback
Exit Sub
End If
Next
db.TransCommit
End Sub示例 4:动态查询
vb
Function GetUsers(Optional sName As String = "", _
Optional lMinAge As Long = 0, _
Optional sStatus As String = "") As Collection
Set GetUsers = New Collection
Dim sSql As String
sSql = "SELECT * FROM users WHERE 1=1"
' 动态构建 SQL 和参数
If sName <> "" Then
sSql = sSql & " AND name LIKE ?"
End If
If lMinAge > 0 Then
sSql = sSql & " AND age >= ?"
End If
If sStatus <> "" Then
sSql = sSql & " AND status = ?"
End If
' 设置 SQL
db.Sql(sSql)
' 添加参数
If sName <> "" Then
db.Param("name", "%" & sName & "%", adVarWChar)
End If
If lMinAge > 0 Then
db.Param("age", lMinAge, adInteger)
End If
If sStatus <> "" Then
db.Param("status", sStatus, adVarWChar)
End If
' 执行查询
If db.QueryParam Then
Set GetUsers = db.Rows
End If
End Function最佳实践
1. 始终使用参数化查询处理用户输入
vb
' ? 推荐:使用参数化查询
db.Sql("SELECT * FROM users WHERE name = ?") _
.Param("name", txtName.Text, adVarWChar) _
.QueryParam
' ? 不推荐:直接拼接用户输入
db.Sql("SELECT * FROM users WHERE name = '" & txtName.Text & "'").Query2. 选择正确的数据类型
vb
' ? 推荐:明确指定数据类型
db.Param("age", 25, adInteger)
db.Param("name", "张三", adVarWChar)
db.Param("price", 99.99, adDecimal)
' ? 不推荐:使用默认类型(可能类型不匹配)
db.Param("age", 25) ' 默认 adVarChar,可能出错3. 使用 Unicode 字符串类型
vb
' ? 推荐:使用 adVarWChar 支持中文
db.Param("name", "张三", adVarWChar)
' ? 不推荐:使用 adVarChar(可能中文乱码)
db.Param("name", "张三", adVarChar)4. 参数顺序要与 SQL 中的占位符顺序一致
vb
' ? 正确:参数顺序与 ? 顺序一致
db.Sql("SELECT * FROM users WHERE name = ? AND age > ?") _
.Param("name", "张三", adVarWChar) _
.Param("age", 18, adInteger)
' ? 错误:参数顺序错误
db.Sql("SELECT * FROM users WHERE name = ? AND age > ?") _
.Param("age", 18, adInteger) _
.Param("name", "张三", adVarWChar)5. 清理参数
vb
' 注意:ExecParam、QueryParam、FetchParam 会自动清理参数(调用 ClearParam)
' 如果只调用 Param 而不执行,需要手动调用 ClearParam 清理:
db.ClearParam常见问题
Q1: 参数化查询比直接拼接慢吗?
回答: 不会,参数化查询通常更快,因为:
- 数据库可以缓存执行计划
- 减少了 SQL 解析时间
- 避免了字符串拼接开销
Q2: 如何在 LIKE 查询中使用参数?
vb
' ? 正确:在参数值中包含通配符
db.Sql("SELECT * FROM users WHERE name LIKE ?") _
.Param("name", "%" & sKeyword & "%", adVarWChar) _
.QueryParam
' ? 错误:在 SQL 中使用通配符
db.Sql("SELECT * FROM users WHERE name LIKE '%?%'") _
.Param("name", sKeyword, adVarWChar) _
.QueryParamQ3: 如何处理 NULL 值?
vb
' 使用 Null 值
db.Sql("SELECT * FROM users WHERE email = ?") _
.Param("email", Null, adVarWChar) _
.QueryParam
' 或者使用 IS NULL
db.Sql("SELECT * FROM users WHERE email IS NULL").QueryQ4: 参数化查询支持 IN 子句吗?
vb
' 注意:ADO 参数化查询对 IN 子句支持有限
' 建议:使用多个 OR 条件或动态构建 SQL
' 方法 1:使用多个 OR
db.Sql("SELECT * FROM users WHERE id = ? OR id = ? OR id = ?") _
.Param("id1", 1, adInteger) _
.Param("id2", 2, adInteger) _
.Param("id3", 3, adInteger) _
.QueryParam
' 方法 2:动态构建(需要验证输入)
Dim sIds As String
sIds = "1,2,3" ' 需要验证格式
db.Sql("SELECT * FROM users WHERE id IN (" & sIds & ")").Query最后更新: 2026-01-21