
本文详解如何在 php 中正确使用 pdo 参数化查询,避免 sql 注入风险,强调列名与值的区分处理:列名不可参数化,必须白名单校验或硬编码;而字段值必须始终通过占位符(? 或命名参数)安全绑定。
本文详解如何在 php 中正确使用 pdo 参数化查询,避免 sql 注入风险,强调列名与值的区分处理:列名不可参数化,必须白名单校验或硬编码;而字段值必须始终通过占位符(? 或命名参数)安全绑定。
在使用 PDO 执行 SQL 语句时,一个常见误区是试图将列名(如字段名、表名、WHERE 条件中的列标识)当作参数传入 prepare() 和 execute()。但需明确:PDO 的参数化机制仅支持数据值(value),不支持标识符(identifier)——即列名、表名、ORDER BY 字段、WHERE 子句中的列引用等均不能用 ? 或 :param 占位符动态替换。
您原始代码中的问题在于:
$sql = "UPDATE village SET `$res` = ? WHERE `$villageID` = ? AND `$fabrik` = ?";
这里 $res、$villageID、$fabrik 都被当作列名拼接到 SQL 字符串中,属于字符串拼接 + 未校验变量,极易引发 SQL 注入(例如传入 $res = 'gold_amount, --' 就可能截断语句)。更严重的是,$villageID 是整数 ID,却错误地被当作了列名(WHERE$villageID= ?),这会导致语法错误——您实际想表达的是 WHERE villageID = ?。
✅ 正确做法分两步:
-
列名必须静态定义或严格白名单校验
若需动态更新不同资源字段(如'wood','stone','gold','food'),应预先定义允许的列名集合,并验证输入:private $allowedResources = ['wood', 'stone', 'gold', 'food', 'iron', 'crop']; public function setVillageRes(int $villageID, int $ownerID, string $res, mixed $newValue, string $fabrik = 'villageID') { // 1. 校验资源字段是否合法 if (!in_array($res, $this->allowedResources)) { throw new InvalidArgumentException("Invalid resource column: '$res'"); } // 2. 校验 fabrik 是否为合法列名(如 'villageID', 'ownerID', 'region' 等) $allowedFabriks = ['villageID', 'ownerID', 'region']; if (!in_array($fabrik, $allowedFabriks)) { throw new InvalidArgumentException("Invalid filter column: '$fabrik'"); } // 3. 先校验村庄归属(防越权) $checkStmt = $this->pdo->prepare("SELECT 1 FROM village WHERE villageID = ? AND ownerID = ?"); $checkStmt->execute([$villageID, $ownerID]); if (!$checkStmt->fetch()) { return "RE-3001"; // 权限不足或记录不存在 } // 4. 安全构建 SQL:列名硬编码/白名单,仅值参数化 $sql = "UPDATE village SET `$res` = ? WHERE `villageID` = ? AND `$fabrik` = ?"; $stmt = $this->pdo->prepare($sql); $stmt->execute([$newValue, $villageID, $fabrik]); // ✅ $newValue 是值,安全绑定 return $stmt->rowCount() > 0 ? "OK" : "NO_UPDATE"; } -
关键原则总结
- ✅ 允许参数化的:所有数据值(如
$newValue,$villageID,$fabrik的值)→ 使用?或:name - ❌ 禁止参数化的:所有SQL 标识符(列名、表名、数据库名、ORDER BY 字段等)→ 必须通过白名单、正则匹配或枚举校验后拼接
- ⚠️ 特别注意:
$villageID是整数 ID,它在 WHERE 条件中应作为值出现(WHERE villageID = ?),而非列名(WHERE$villageID= ?是错误的)
- ✅ 允许参数化的:所有数据值(如
? 进阶建议:若资源字段极多或结构易变,可考虑使用 JSON 字段存储资源(如 resources JSON),用 JSON_SET() 更新,进一步减少动态列名需求:
UPDATE village SET resources = JSON_SET(resources, '$.wood', ?) WHERE villageID = ?;
这样只需一个参数化字段,彻底规避列名拼接风险。
总之,安全的 SQL 参数化 = 值用 ?,标识符靠白名单。坚守这一原则,才能兼顾灵活性与安全性。

















