10bet网址
MySQL PHP API
下载本手册

3.2.4准备报表

PHP文档组版权所有。10bet官方网站

MySQL数据库支持预制语句。准备语句或参数化语句可以高效地重复执行同一条语句,防止SQL注入。

基本工作流程

预准备语句的执行包括两个阶段:准备和执行。在准备阶段,语句模板被发送到数据库服务器。服务器执行语法检查并初始化服务器内部资源以供以后使用。

MySQL服务器支持使用匿名的位置占位符?

Prepare后面跟着execute。在执行过程中,客户端绑定参数值并将它们发送给服务器。服务器使用之前创建的内部资源执行带有绑定值的语句。

例3.11准备好的语句

< ?php MYSQLI_REPORT_ERROR (MYSQLI_REPORT_STRICT);$mysqli = new mysqli("example.com", "user", "password", "database");/*非准备语句*/ $mysqli->查询(DROP TABLE IF EXISTS test);$mysqli->查询("CREATE TABLE test(id INT, label TEXT)");/*准备语句,阶段1:准备*/ $stmt = $mysqli->准备("INSERT INTO test(id, label) VALUES (??)”);/*准备好的语句,第二阶段:绑定并执行*/ $id = 1;$label = 'PHP';$stmt->bind_param("is", $id, $label);// "is"表示$id绑定为整数,$label绑定为字符串$stmt->execute();


重复执行

预处理语句可以重复执行。每次执行时,绑定变量的当前值都会被计算并发送到服务器。该语句不会再次解析。语句模板不会再次传输到服务器。

例3.12 INSERT准备一次,执行多次

< ?php MYSQLI_REPORT_ERROR (MYSQLI_REPORT_STRICT);$mysqli = new mysqli("example.com", "user", "password", "database");/*非准备语句*/ $mysqli->查询(DROP TABLE IF EXISTS test);$mysqli->查询("CREATE TABLE test(id INT, label TEXT)");/*准备语句,阶段1:准备*/ $stmt = $mysqli->准备("INSERT INTO test(id, label) VALUES (??)”);/*准备好的语句,第二阶段:绑定并执行*/ $stmt->bind_param("is", $id, $label);// "is"表示$id绑定为整数,$label绑定为字符串$data = [1 => 'PHP', 2 => 'Java', 3 => ' c++ '];Foreach ($data as $id => $label) {$stmt->execute();} $result = $mysqli->查询('SELECT id, label FROM test'); var_dump($result->fetch_all(MYSQLI_ASSOC));

上面的例子将输出:

数组(3){[0]=>数组(2){["id"]=>字符串(1)"1" ["label"]=> string(3)"PHP"}[1]=>数组(2){["id"]=>字符串(1)"2" ["label"]=> string(4)"Java"} [2]=> array(2) {["id"]=> string(1)"3" ["label"]=> string(3)" c++ "}}


每个预处理语句都会占用服务器资源。语句应该在使用后立即显式地关闭。如果没有显式地执行,当PHP释放语句句柄时,该语句将被关闭。

使用准备语句并不总是执行语句的最有效方式。只执行一次的准备语句比非准备语句造成更多的客户机-服务器往返。这就是为什么选择不能作为上面的准备语句运行。

另外,请考虑使用MySQL multi-INSERT SQL语法进行insert。例如,multi-INSERT在服务器和客户机之间需要的往返次数比上面所示的准备语句要少。

例3.13使用多insert SQL减少往返次数

< ?php MYSQLI_REPORT_ERROR (MYSQLI_REPORT_STRICT);$mysqli = new mysqli("example.com", "user", "password", "database");$mysqli->查询(“DROP TABLE IF EXISTS test”);$mysqli->查询("CREATE TABLE test(id INT)");$values = [1,2,3,4];= $美元支撑mysqli - >准备(“插入测试(id)值  (?), (?), (?), (?)");- > bind_param美元支撑(‘iiii’,……美元值);支撑- > execute ();


结果集值数据类型

MySQL客户端服务器协议为准备语句和非准备语句定义了不同的数据传输协议。准备语句使用所谓的二进制协议。MySQL服务器以二进制格式发送结果集数据。结果在发送之前不会序列化为字符串。客户端库接收二进制数据,并尝试将值转换为适当的PHP数据类型。例如,SQL语句的结果INT列将作为PHP整数变量提供。

例3.14原生数据类型

< ?php MYSQLI_REPORT_ERROR (MYSQLI_REPORT_STRICT);$mysqli = new mysqli("example.com", "user", "password", "database");/*非准备语句*/ $mysqli->查询(DROP TABLE IF EXISTS test);$mysqli->查询("CREATE TABLE test(id INT, label TEXT)");$mysqli->查询("INSERT INTO test(id, label) VALUES (1, 'PHP')");$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");支撑- > execute ();$result = $stmt->get_result();$row = $result->fetch_assoc(); printf("id = %s (%s)\n", $row['id'], gettype($row['id'])); printf("label = %s (%s)\n", $row['label'], gettype($row['label']));

上面的例子将输出:

id = 1(整数)label = PHP(字符串)


这种行为不同于非准备语句。默认情况下,非准备语句以字符串形式返回所有结果。可以使用连接选项更改此默认值。如果使用连接选项,则没有区别。

使用绑定变量获取结果

预处理语句的结果可以通过绑定输出变量来检索,也可以通过请求mysqli_result对象。

语句执行后必须绑定输出变量。必须为语句结果集的每一列绑定一个变量。

例3.15输出变量绑定

< ?php MYSQLI_REPORT_ERROR (MYSQLI_REPORT_STRICT);$mysqli = new mysqli("example.com", "user", "password", "database");/*非准备语句*/ $mysqli->查询(DROP TABLE IF EXISTS test);$mysqli->查询("CREATE TABLE test(id INT, label TEXT)");$mysqli->查询("INSERT INTO test(id, label) VALUES (1, 'PHP')");$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");支撑- > execute ();支撑- > bind_result (out_id, out_label美元);虽然(支撑美元——> fetch ()) {printf (" id = % s (% s),标签= % s (% s) \ n ", out_id美元,方法(out_id美元),out_label美元,方法(out_label美元)); }

上面的例子将输出:

id = 1(整数),label = PHP(字符串)


预处理语句默认情况下返回未缓冲的结果集。语句的结果不会隐式地从服务器获取并传输到客户端进行客户端缓冲。结果集接受服务器资源,直到所有结果都被客户端获取。因此建议及时使用结果。如果客户端无法获取所有结果,或者客户端在获取所有数据之前关闭语句,则必须隐式地通过mysqli

也可以使用缓冲区预处理语句的结果mysqli_stmt: store_result

使用mysqli_result接口获取结果

不使用绑定结果,还可以通过mysqli_result接口检索结果。mysqli_stmt: get_result返回一个缓冲的结果集。

3.16使用mysqli_result获取结果

< ?php MYSQLI_REPORT_ERROR (MYSQLI_REPORT_STRICT);$mysqli = new mysqli("example.com", "user", "password", "database");/*非准备语句*/ $mysqli->查询(DROP TABLE IF EXISTS test);$mysqli->查询("CREATE TABLE test(id INT, label TEXT)");$mysqli->查询("INSERT INTO test(id, label) VALUES (1, 'PHP')");$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");支撑- > execute ();$result = $stmt->get_result();var_dump(结果- > fetch_all (MYSQLI_ASSOC));

上面的例子将输出:

数组(1){[0]= >阵列(2){(" id ") int = >(1)["标签"]= >字符串(3)php}}


使用mysqli_result接口提供了灵活的客户端结果集导航的额外好处。

例3.17灵活读取的缓冲结果集

< ?php MYSQLI_REPORT_ERROR (MYSQLI_REPORT_STRICT);$mysqli = new mysqli("example.com", "user", "password", "database");/*非准备语句*/ $mysqli->查询(DROP TABLE IF EXISTS test);$mysqli->查询("CREATE TABLE test(id INT, label TEXT)");mysqli - >查询(“插入测试(id、标签)值(1,PHP),(2,“Java”),(3“c++”)");$stmt = $mysqli->prepare("SELECT id, label FROM test");支撑- > execute ();$result = $stmt->get_result();$row_no = $result->num_rows - 1; $row_no >= 0; $row_no--) { $result->data_seek($row_no); var_dump($result->fetch_assoc()); }

上面的例子将输出:

数组(2){["id"]=> int(3) ["label"]=> string(3)“c++”}阵列(2){(" id ") int = >(2)["标签"]= >字符串(4)“Java”}(2)数组{(" id ") int = >(1)["标签"]= >字符串(3)“PHP”}


转义和SQL注入

绑定变量与查询分开发送到服务器,因此不会干扰查询。在解析语句模板之后,服务器在执行点直接使用这些值。绑定参数不需要转义,因为它们从未被直接替换到查询字符串中。必须向服务器提供绑定变量类型的提示,以创建适当的转换。看到mysqli_stmt: bind_param函数获取更多信息。

这种分离有时被认为是防止SQL注入的唯一安全特性,但如果所有值的格式都正确,则可以使用非准备语句实现相同程度的安全。应该注意的是,正确的格式与转义不同,它涉及的逻辑比简单的转义更多。因此,预处理语句是实现数据库安全性的一种更方便、更不易出错的方法。

客户端准备语句模拟

API不包括用于客户端预处理语句模拟的模拟。

快速比较准备好的和非准备好的语句

下表比较了服务器端准备语句和非准备语句。

表3.2准备语句和非准备语句的比较

事先准备好的声明中 Non-prepared声明
客户端-服务器往返,SELECT,单次执行 2 1
从客户端传输到服务器的语句字符串 1 1
客户端-服务器往返,SELECT,重复(n)执行 1 + n n
从客户端传输到服务器的语句字符串 1个模板,n个绑定参数,如果有的话 N次,每次都解析
输入参数绑定API 是的 不,手动输入转义
输出变量绑定API 是的 没有
支持使用mysqli_result API 是的,使用mysqli_stmt: get_result 是的
缓冲的结果集 是的,使用mysqli_stmt: get_result或与mysqli_stmt: store_result 是的,默认为mysqli:查询
未缓冲的结果集 是的,使用输出绑定API 是的,使用mysqli: real_querymysqli: use_result
MySQL客户端服务器协议数据传输风格 二进制协议 文本协议
结果集值SQL数据类型 取回时保存 在取回时转换为字符串或保留
支持所有SQL语句 最新的MySQL版本支持大部分,但不是全部 是的

另请参阅

mysqli: __construct
mysqli:查询
mysqli:准备
mysqli_stmt:准备
mysqli_stmt:执行
mysqli_stmt: bind_param
mysqli_stmt: bind_result