PREPARE — 为执行准备一个语句
PREPAREname[ (data_type[, ...] ) ] ASstatement
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文档。
尽管预备语句的主要目的在于避免对语句重复进行解析分析和规划,但PostgreSQL会在使用前强制重新分析并重新规划该语句,只要其中使用的数据库对象自上次使用该预备语句以来发生了定义(DDL)变更。此外,如果 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 所在的使用上下文中推断出来。
SQL 标准包含PREPARE语句,但它只用于嵌入式 SQL。这里的PREPARE语句在语法上也略有不同。