PREPARE
描述
PREPARE创建一个预备语句。预备语句是一种服务器端对象,它可以被用来优化性能。当PREPARE语句被执行时,指定的语句会被解析、分析并且重写。当后续发出一个EXECUTE命令时,该预备语句会被规划并且执行。这种工作的划分避免了重复性的解析分析工作,不过允许执行计划依赖所提供的特定参数值。
预备语句可以接受参数:在执行时会被替换到语句中的值。在创建预备语句时,可以用位置引用参数,如$1、$2等。也可以选择性地指定参数数据类型的一个列表。当一个参数的数据类型没有被指定或者被声明为unknown时,其类型会从该参数被使用的环境中推知(如果可能)。在执行该语句时,在EXECUTE语句中为这些参数指定实际值。更多有关于此的信息可参考EXECUTE。
预备语句只在当前数据库会话期间存在。当会话结束时,预备语句会消失,因此在重新使用之前必须重新建立它。这也意味着一个预备语句不能被多个数据库客户端同时使用。不过,每一个客户端可以创建它们自己的预备语句来使用。预备语句可以用DEALLOCATE命令手工清除。
当一个会话要执行大量类似语句时,预备语句可能会有最大性能优势。如果该语句很复杂(难于规划或重写),例如,如果查询涉及很多表的连接或者要求应用多个规则,性能差异将会特别明显。如果语句相对比较容易规划和重写,但是执行起来开销相对较大,那么预备语句的性能优势就不那么显著了。
参数
- name
-
给这个特定预备语句的任意名称。它在一个会话中必须唯一并且后续将被用来执行或者清除一个之前准备好的语句。
- data_type
-
预备语句一个参数的数据类型。如果一个特定参数的数据类型没有被指定或者被指定为unknown,将从该参数被使用的环境中推得。要在预备语句本身中引用参数,可以使用 $1、$2等。
- statement
-
任何SELECT、INSERT、UPDATE、DELETE或者VALUES语句。
注解
对每一组提供的EXECUTE值,预备语句可以使用通用计划而不是重新做计划。对于没有参数的预备语句马上就会这样做,否则只有五次或者更多次执行产生的计划的估计代价平均值(包括规划开销)比通用计划的代价估计更昂贵时才会这样做。一旦选中一个通用计划,在该预备语句剩余的生存时间内都将使用它。使用在重复值很多的列中很少出现的EXECUTE值可以产生比通用计划更加廉价的定制计划(即使加上规划开销),这样通用计划将不会被使用。
通用计划假定每一个提供给EXECUTE的值都是该列的可区分值并且列值是均匀分布的。例如,如果统计信息记录了三个可区分的列值,通用计划会假定一个列等值比较将匹配被处理行中的 33%。列统计值也允许通用计划准确地计算唯一列的选择度。如果一个通用计划被选中时,在非均匀分布列上的比较以及制定不存在的值都会影响平均计划代价。
要检查PostgreSQL为一个预备语句使用的查询计划,可以使用EXPLAIN,例如EXPLAIN EXECUTE。如果使用的是一个通用计划,它将包含参数符号$n,而一个定制计划则会把提供的参数值替换进去。通用计划中的行估计值反映了这些参数计算出来的选择度。
更多关于查询规划以及PostgreSQL为此所收集的统计信息的内容,请见ANALYZE文档。
尽管预备语句主要是为了避免重复对语句进行解析分析以及规划,但是只要上一次使用该预备语句后该语句中用到的数据库对象发生了定义性(DDL)改变,PostgreSQL将会对该语句强制进行重新分析和重新规划。还有,如果search_path的值发生变化,也将使用新的search_path重新解析该语句(后一种行为是从PostgreSQL 9.3 开始的新行为)。这些规则让预备语句的使用在语义上几乎等效于反复提交相同的查询文本,但是能在性能上获利(如果没有对象定义被改变,特别是如果最优计划保持不变时)。该语义等价性不完美的一个例子是:如果语句用一个未限定的名称引用表,并且之后在search_path中更靠前的模式中创建了一个新的同名表,则不会发生自动的重解析,因为该语句使用的对象没有被改变。不过,如果某些其他更改造成了重解析,后续使用中都会引用新表。
可以通过查询pg_prepared_statements系统视图来看到会话中所有可用的预备语句。
例子
为一个INSERT语句创建一个预备语句,然后执行它:
PREPARE fooplan (int, text, bool, numeric) AS INSERT INTO foo VALUES($1, $2, $3, $4); EXECUTE fooplan(1, 'Hunter Valley', 't', 200.00);
为一个SELECT语句创建一个预备语句,然后执行它:
PREPARE usrrptplan (int) AS SELECT * FROM users u, logs l WHERE u.usrid=$1 AND u.usrid=l.usrid AND l.date = $2; EXECUTE usrrptplan(1, current_date);
注意第二个参数的数据类型没有被指定,因此会从使用$2的环境中推知。