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