“PDO 的无结果集语句”是数据库操作中一类不返回数据行、仅需执行并获取操作状态或影响行数的 SQL 指令。
一、定义:什么是“无结果集语句”?
在 SQL 标准中,语句可分为两类:
| 类型 | 说明 | 是否返回结果集 |
|---|---|---|
| DQL(Data Query Language) | SELECT、SHOW等查询语句 | ✅ 是 |
| DML / DDL / DCL | INSERT、UPDATE、DELETE、CREATE、DROP、GRANT等 | ❌ 否 |
“无结果集语句”特指执行后不返回数据行(rows)的 SQL,PDO 提供两种方式执行:
PDO::exec($sql)—— 专为此类语句设计PDO::prepare($sql)->execute()—— 通用方式(也可用于有结果集语句)
⚠️ 注意:
INSERT ... RETURNING(PostgreSQL)或INSERT ... SELECT LAST_INSERT_ID()(MySQL)等虽属 DML,但可返回值,需特殊处理。
二、典型语句分类
| 类别 | 示例 | 是否可绑定参数 |
|---|---|---|
| DML(数据操作) | INSERT INTO users (name) VALUES (?)UPDATE users SET name = ? WHERE id = ?DELETE FROM logs WHERE created_at < ? | ✅ 是(应使用绑定) |
| DDL(数据定义) | CREATE TABLE tmp (id INT)ALTER TABLE users ADD COLUMN bio TEXTDROP TABLE tmp | ❌ 否(标识符无法绑定) |
| DCL / 管理命令 | GRANT SELECT ON db.* TO userTRUNCATE TABLE logsSET sql_mode = 'STRICT' | ❌ 否 |
🔑关键洞察:
只有 DML 语句中的“值”可安全绑定;DDL/DCL 中的“表名、列名、权限对象”属于标识符(identifier),PDO 无法绑定,必须通过白名单或转义处理。
三、执行机制:PDO 如何处理无结果集语句?
1.PDO::exec()路径
$count=$pdo->exec("DELETE FROM users WHERE active = 0");- 内部流程:
- 直接发送完整 SQL 到数据库
- 执行后返回受影响的行数(
int) - 不支持参数绑定→ 若拼接用户输入,危险!
2.prepare() + execute()路径
$stmt=$pdo->prepare("DELETE FROM users WHERE email = ?");$stmt->execute(['spam@example.com']);$count=$stmt->rowCount();// 获取影响行数- 内部流程:
prepare():发送 SQL 模板到数据库(解析、优化)execute():发送绑定参数,执行- 支持参数绑定→ 安全
- 通过
rowCount()获取影响行数
✅推荐:所有含动态值的 DML 语句,必须走
prepare + execute。
四、返回值语义:exec()vsrowCount()
| 方法 | 成功返回 | 失败返回 | 说明 |
|---|---|---|---|
PDO::exec($sql) | 受影响的行数(int) | false | 仅适用于无结果集语句 |
PDOStatement::rowCount() | 受影响的行数(int) | —— | 仅在execute()后调用 |
⚠️ 重要限制:
rowCount()在SELECT上的行为未定义(某些驱动返回匹配行数,但不可靠)- 部分 DDL 语句(如
CREATE TABLE)影响行数为 0,但执行成功 INSERT的自增 ID 需通过lastInsertId()获取,而非rowCount()
// 正确获取自增 ID$pdo->exec("INSERT INTO users (name) VALUES ('John')");$id=$pdo->lastInsertId();// ← 关键!五、安全边界:无结果集 ≠ 无风险
1.DML 风险:值注入
// ❌ 危险$email=$_POST['email'];$pdo->exec("DELETE FROM users WHERE email = '$email'");// 注入点!✅ 安全做法:
$stmt=$pdo->prepare("DELETE FROM users WHERE email = ?");$stmt->execute([$email]);2.DDL/DCL 风险:标识符注入
// ❌ 危险(无法用绑定)$table=$_GET['table'];$pdo->exec("DROP TABLE$table");// 可能 DROP users!✅ 安全做法:
- 白名单校验:
$allowed=['tmp_logs','cache'];if(!in_array($table,$allowed))thrownewException('Invalid table'); - 转义标识符(Laravel 的
Grammar::wrapTable()):$wrapped="`".str_replace('`','``',$table)."`";$pdo->exec("DROP TABLE$wrapped");
🔒原则:无结果集语句的“动态部分”必须明确区分是“值”还是“标识符”,并采用对应防护策略。
六、Laravel 中的工程实践
Laravel 对无结果集语句做了安全封装与抽象:
1.DML 自动走 prepare + execute
DB::table('users')->where('email',$email)->delete();// 安全绑定- 底层调用
Connection::delete()→PDO::prepare() + execute()
2.DDL 由 Schema Builder 生成,无用户输入
Schema::create('orders',function(Blueprint$table){$table->id();$table->string('status');});- SQL 由 Laravel 构造,不暴露原生 DDL 给业务层
3.原生语句需显式处理
// 安全(带绑定)DB::statement('UPDATE users SET verified = ? WHERE id = ?',[1,$id]);// 危险(需自行确保 $table 安全)DB::statement("DROP TABLE$table");4.获取影响行数
$result=DB::update('UPDATE users SET name = ? WHERE id = ?',['John',1]);// 注意:Laravel 的 update() 返回 bool,非行数!// 若需行数,需用原生 PDO 或扩展 Connection💡 Laravel 的设计哲学:让安全路径成为默认路径,危险操作需显式且谨慎。
总结:无结果集语句的“牛体解剖图”
| 维度 | 要点 |
|---|---|
| 本质 | 不返回数据行的 SQL(DML/DDL/DCL) |
| 执行方式 | exec()(简单但危险) vsprepare+execute()(安全通用) |
| 返回值 | 影响行数(exec()直接返回,execute()通过rowCount()) |
| 安全核心 | DML 用参数绑定,DDL 用白名单/转义 |
| Laravel 封装 | DML 自动安全绑定,DDL 由框架生成,隔离风险 |
| 常见陷阱 | 混淆lastInsertId()与rowCount();在 DDL 中拼接未校验输入 |
🔪庖丁之刀:
无结果集语句的“无结果”,不等于“无风险”。
用prepare + execute守住 DML,用白名单/转义守住 DDL,方得安全与效能兼备。