pdo的attr_emulate_prepares控制着预处理语句的执行方式:开启时由php模拟(true),关闭时由数据库驱动执行(false)。主要区别在于参数绑定和sql注入防护,模拟预处理会将参数拼接到sql中发送,而真预处理会分离sql和参数。推荐使用真预处理以确保安全性。

模拟预处理的工作流程是怎样的
当ATTR_EMULATE_PREPARES设置为true时,PDO会在客户端将SQL语句和参数合并成一个完整的SQL字符串发送给数据库服务器。这意味着数据库服务器接收到的是一条已经拼接好的SQL,它无法区分哪部分是代码,哪部分是数据。这种方式虽然兼容性好,但存在SQL注入风险,因为如果参数处理不当,攻击者可能通过构造特殊输入来改变SQL逻辑。

\$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
\$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
\$stmt = \$pdo->prepare('SELECT * FROM users WHERE username = ?');
\$stmt->execute(['admin']);
\$result = \$stmt->fetchAll();
print_r(\$result);
上述代码在模拟预处理模式下,PDO会将'SELECT * FROM users WHERE username = ?'和'admin'合并为'SELECT * FROM users WHERE username = \'admin\''发送给MySQL。如果输入是' OR 1=1 --,则可能引发注入。
真预处理的工作流程是怎样的
当ATTR_EMULATE_PREPARES设置为false时,PDO会向数据库服务器发送原始的SQL模板,然后单独发送参数列表。数据库服务器使用其内部的预处理机制来解析和执行SQL,确保参数不会被解释为SQL代码。这种方式提供了更强的SQL注入防护,因为参数和数据是完全分离的。

\$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
\$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
\$stmt = \$pdo->prepare('SELECT * FROM users WHERE username = ?');
\$stmt->execute(['admin']);
\$result = \$stmt->fetchAll();
print_r(\$result);
上述代码在真预处理模式下,PDO会发送'SELECT * FROM users WHERE username = ?'作为SQL模板,然后单独发送'admin'作为参数。即使输入是' OR 1=1 --,数据库也会将其视为普通字符串,不会改变SQL逻辑。
不同预处理模式对性能有何影响
模拟预处理和真预处理在性能上有所不同。模拟预处理由于需要在客户端进行SQL拼接,可能会增加CPU开销,尤其是在处理大量参数时。真预处理则由数据库服务器处理,通常更高效,特别是对于重复执行的SQL语句,数据库可以缓存执行计划。然而,对于一次性查询,两者的性能差异可能不明显。选择哪种模式应根据具体应用场景和安全需求来决定。











