# [更新] `5.1.25`查询参数绑定的改进 >[danger] `V5.1.25`版本已经发布了,该版本主要对查询的参数绑定做了一些优化和改进,本文略为讲解下一些改进的细节以及注意事项。 ## 自动参数绑定 可能很多开发者在使用`ThinkPHP`的时候并没有特别注意到查询参数绑定,因为几乎所有的查询都是自动参数绑定的。例如,下面是一个很普通的查询: ``` Db::name('user')->where('id', 10)->find(); ``` 抛开如何把查询构造器语法解析为最终的SQL,实际在执行最终的解析SQL的时候会先经过下面的三个步骤。 ``` // 预处理 $PDOStatement = $PDO->prepare('SELECT * FROM user WHERE id = :id'); // 参数绑定 $PDOStatement->bindValue('id', 10, PDO::PARAM_INT); // 执行SQL查询 $PDOStatement->execute(); ``` 而自动参数绑定其实是在解析最终SQL的时候处理完成的,也就是把`id`的实际值使用`:id`占位符处理,然后在绑定数据中进行命名占位符的绑定对应,最终交由`bindValue`方法统一处理。 因此,基本上你不需要关注参数绑定的存在,也不需要进行手动参数绑定,只有在少数原生查询或者字符串条件的情况下,才需要手动进行参数绑定,一般来说有下面两种参数绑定方式。 ``` // 使用?占位符绑定 Db::query("select * from think_user where id=? AND status=?", [8, 1]); Db::name('user') ->where('where id=? AND status=?', [8,1]) ->select(); // 使用命名占位符绑定 Db::name('user') ->where('where id=:id AND status=:status', ['id' => 8, 'status' => 1]) ->select(); Db::execute("update think_user set name=:name where status=:status", ['name' => 'thinkphp', 'status' => 1]); ``` >[danger] 除了上述的手动绑定方式外,`Query`类还提供了一个`bind`方法用于指定查询的参数绑定,但并不建议使用,因为使用查询表达式查询的数据都会进行自动参数绑定,这个情况下进行手动绑定是多余的,对性能也不会有任何的提升。 ## 性能优化 因为`5.1`版本一直以来为了直观,采用的都是命名(占位符)的绑定方式,但由于命名绑定的方式需要检测是否存在相同的命名,以及做出避免冲突的处理,从而带来了一定的性能开销,这个性能开销在你的数据量特别大的情况下(例如`insertAll`了大量的数据)会变得非常明显,而真正开销最大的地方其实是在每次获取实际的查询SQL都会进行命名绑定的参数替换。 >[info] 出于性能的优化考虑,`5.1.25`版本开始底层改为`?`占位符处理查询参数绑定,但并不影响你在**使用原生查询**的时候使用命名占位符绑定。 此次改进后,对于大量数据的查询性能会有明显的提升。之前版本如果使用`insertAll()`方法批量写入1000条数据的性能开销在`3s`左右(这是一个几乎奔溃的性能),性能甚至远远不如模型类的`saveAll()`方法大约`0.04s`(因为`saveAll`方法是每次写入一条数据)。改进后使用`insertAll()`方法批量写入1000条数据的性能开销在`0.03s`左右,性能略好于模型类的`saveAll()`方法大约`0.04s`,效果还是比较显著的。 如果你不幸暂时没有办法升级到`5.1.25`版本以上,那么建议你在使用`insertAll`方法批量写入大数据的时候,改为下面的方式。 ``` // 多次写入,每次写入100条 Db::name('user')->limit(100)->insertAll($data); ``` 那么有什么注意事项呢?我们知道,PDO的参数绑定是不支持`?`占位符和命名占位符混合使用的。当你在查询构造器中使用了命名占位符的话,系统会自动转化为`?`占位符的方式,包括下面几种情况。 ``` // 使用命名占位符绑定 系统会自动转换为?占位符方式 // 因此必须确保你的变量绑定顺序和占位符顺序一致 // 正确 Db::name('user') ->where('where id=:id AND status=:status', ['id' => 8, 'status' => 1]) ->select(); // 正确 Db::name('user') ->where('where id=? AND status=?', [ 8, 1]) ->select(); // 正确但不建议使用 Db::name('user') ->where('where id=:id AND status=?', ['id' => 8, 1]) ->select(); // 错误 Db::name('user') ->where('where id=:id AND status=:status', [ 'status' => 1, 'id' => 8]) ->select(); ``` 另外,查询构造器的`whereExp`和`whereRaw`方法中的参数绑定也会进行自动转换。 ``` Db::name('user') ->whereRaw('where id=:id AND status=:status', ['id' => 8, 'status' => 1]) ->select(); ``` 但原生查询则不做任何转换(只要你使用统一的占位符方式即可),因此下面的手动参数绑定不做任何处理,并且依然有效。 ``` // 使用?占位符绑定 Db::query("select * from think_user where id=? AND status=?", [8, 1]); // 使用命名占位符绑定 Db::execute("update think_user set name=:name where status=:status", ['name' => 'thinkphp', 'status' => 1]); ``` >[danger] 最后,再次强调,不要手动使用`bind`方法进行参数绑定,没有必要,而且会导致不可预知的问题。 ## 浮点型支持 PDO参数绑定对浮点型并没有做特殊的支持,尤其是在PHP`7.2+`版本开始,对`PDO::PARAM_INT`绑定类型更为严格后,导致浮点型会被自动处理为整数。 为了更好的支持浮点型的参数绑定,ThinkPHP`5.1.25`版本引入了一个内置常量`Connection::PARAM_FLOAT`作为内部自动参数绑定的绑定类型,自动对浮点型的数据内部按照`PDO::PARAM_STR`进行参数绑定,但在生成SQL语句的时候仍然按照浮点型数据进行处理。