本文介绍了PREPARE的语法、参数以及示例等内容。
简介
PREPARE
创建一个预备语句。预备语句是一种服务器端对象,它可以被用来优化性能。当PREPARE
语句被执行时,指定的语句会被解析、分析并且重写。当后续发出一个EXECUTE
命令时,该预备语句会被规划并且执行。这种工作的划分避免了重复性的解析分析工作,不过允许执行计划依赖所提供的特定参数值。
预备语句可以接受参数:在执行时会被替换到语句中的值。在创建预备语句时,可以用位置引用参数,如$1
、$2
等。也可以选择性地指定参数数据类型的一个列表。当一个参数的数据类型没有被指定或者被声明为unknown
时,其类型会从该参数第一次被引用的环境中推知(如果可能)。在执行该语句时,在EXECUTE
语句中为这些参数指定实际值。更多有关于此的信息可参考EXECUTE。
预备语句只在当前数据库会话期间存在。当会话结束时,预备语句会消失,因此在重新使用之前必须重新建立它。这也意味着一个预备语句不能被多个数据库客户端同时使用。不过,每一个客户端可以创建它们自己的预备语句来使用。预备语句可以用DEALLOCATE命令手工清除。
当一个会话要执行大量类似语句时,预备语句可能会有最大性能优势。如果该语句很复杂(难于规划或重写),例如,如果查询涉及很多表的连接或者要求应用多个规则,性能差异将会特别明显。如果语句相对比较容易规划和重写,但是执行起来开销相对较大,那么预备语句的性能优势就不那么显著了。
语法
PREPARE name [ ( data_type [, ...] ) ] AS statement
参数
name
给这个特定预备语句的任意名称。它在一个会话中必须唯一并且后续将被用来执行或者清除一个之前准备好的语句。data_type
预备语句一个参数的数据类型。如果一个特定参数的数据类型没有被指定或者被指定为unknown
,将从该参数第一次被引用的环境中推得。要在预备语句本身中引用参数,可以使用$1
、$2
等。statement
任何SELECT
、INSERT
、UPDATE
、DELETE
或者VALUES
语句。
说明
可以使用generic plan或custom plan执行已准备好的语句。 通用计划在所有执行中都是相同的,而自定义计划是为特定执行所生成的,使用调用中给出的参数值。 使用通用计划可以避免计划开销,但在某些情况下,自定义计划的执行效率要高得多,因为规划器可以利用参数值的知识。 (当然,如果准备好的语句没有参数,则这是没有意义的,并且始终应使用通用计划。)
默认情况下(也就是当 plan_cache_mode 设定为 auto
时),对已经准备好的具有参数的语句,服务器将自动选择使用通用或自定义计划。 当前的规则是,前五次执行都是使用自定义计划完成的,并且计算这些计划的平均估计成本。然后创建通用计划,并将其估计成本与自定义计划的平均成本相比较。 如果通用计划的成本没有比平均自定义计划成本高太多,那么后续执行将使用通用计划,以使重复的再看起来更可取。
这种探索式方法可以重写,强迫服务器使用通用或自定义计划,通过将plan_cache_mode
分别设置为force_generic_plan
或 force_custom_plan
。 如果通用计划的成本估计由于某种原因很不理想,则此设置主要很有用,尽管其实际成本远高于自定义计划的实际成本,可以选择此设置。
要检查PolarDB为一个预备语句使用的查询计划,可以使用EXPLAIN,例如:
EXPLAIN EXECUTE name(parameter_values);
如果使用的是一个通用计划,它将包含参数符号$
n
,而一个定制计划则会把提供的参数值替换进去。
尽管预备语句主要是为了避免重复对语句进行解析分析以及规划,但是只要上一次使用该预备语句后该语句中用到的数据库对象发生了定义性(DDL)改变,PolarDB将会对该语句强制进行重新分析和重新规划。还有,如果 search_path 的值发生变化,也将使用新的search_path
重新解析该语句(后一种行为是从PolarDB 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
的环境中推知。