开发人员有时需要
将一组参数传递给请求,甚至需要
传递整个 “输入”
选择 。 有时会遇到非常奇怪的解决方案。

让我们从相反的角度出发,看看它不值得做什么,为什么以及如何做得更好。
直接将值插入请求主体
通常看起来像这样:
query = "SELECT * FROM tbl WHERE id = " + value
...左右:
query = "SELECT * FROM tbl WHERE id = :param".format(param=value)
关于这种方法,据说,
甚至写得很丰富:

几乎总是这样,这是
通向SQL注入的
直接路径,并且给业务逻辑带来了额外的负担,这被迫“粘合”查询字符串。
仅当需要在PostgreSQL 10及更低版本的PostgreSQL中
使用section以获得更有效的计划时,这种方法才可以部分证明是合理的。 在这些版本中,即使不考虑传输的参数,也仅基于请求主体来确定扫描部分的列表。
$ n参数
使用参数
占位符是好的,它允许您使用
PREPARED STATEMENTS ,减少业务逻辑(查询字符串仅生成和传输一次)和数据库服务器(不需要为每个请求实例重新解析和计划)的负载。
可变数量的参数
当我们想提前传递未知数量的参数时,问题将等待我们:
... id IN ($1, $2, $3, ...)
如果您以这种形式保留请求,那么尽管它可以使我们免于潜在的注入,但仍然需要
根据参数数量对每个选项的请求
进行粘合/解析。 比每次都做的更好,但是您可以不用它。
仅传递一个包含
数组的
序列化表示形式的参数就足够
了 :
... id = ANY($1::integer[])
唯一的区别是需要将参数显式转换为所需的数组类型。 但这不会引起问题,因为我们已经提前知道了要解决的问题。
样品转移(矩阵)
通常,这些都是用于“在一个请求中”将数据集插入数据库的各种选项:
INSERT INTO tbl(k, v) VALUES($1,$2),($3,$4),...
除了上述“重新粘贴”请求问题之外,这还可能导致
内存不足和服务器崩溃。 原因很简单-PG为参数保留了额外的内存,并且集合中的记录数仅受所应用的愿望清单业务逻辑的限制。 在特别是临床情况下,人们不得不看到
“编号”的论点大于9,000美元 -无需这样做。
我们使用
“两级”序列化重写请求:
INSERT INTO tbl SELECT unnest[1]::text k , unnest[2]::integer v FROM ( SELECT unnest($1::text[])::text[]
是的,在数组内部为“复杂”值的情况下,它们需要用引号引起来。
显然,您可以用任意数量的字段“扩展”选择范围。
巢,巢,...
定期有传输选项,而不是几个“列数组”的“数组数组”,我
在上一篇文章中提到
过 :
SELECT unnest($1::text[]) k , unnest($2::integer[]) v;
使用这种方法,在生成不同列的值列表时会出错,因此获得完全
意外的结果非常简单,这也取决于服务器版本:
杰森
从9.3版开始,PostgreSQL引入了完善的函数来处理json类型。 因此,如果在浏览器中定义了输入参数,则可以
在此处
为SQL查询创建一个
json对象 :
SELECT key k , value v FROM json_each($1::json);
对于以前的版本,
每个(hstore)都可以使用相同的方法,但是在hstore中转义复杂对象的正确“卷积”会导致问题。
json_populate_recordset
如果预先知道来自“输入” json数组的数据将填充某种表,则可以使用json_populate_recordset函数在“解引用”字段并将其转换为必要的类型方面节省很多:
SELECT * FROM json_populate_recordset( NULL::pg_class , $1::json
json_to_recordset
而且此函数只是简单地将转移的对象数组“扩展”到选择中,而无需依赖表格式:
SELECT * FROM json_to_recordset($1::json) T(k text, v integer);
临时表
但是,如果传输的样本中的数据量非常大,则将其放入一个序列化参数很困难,有时甚至是不可能的,因为它需要一次性
分配大量内存 。 例如,您需要长期,长时间地从外部系统收集有关事件的大数据包,然后希望在数据库端对其进行一次处理。
在这种情况下,最好的解决方案是使用
临时表 :
CREATE TEMPORARY TABLE tbl(k text, v integer); ... INSERT INTO tbl(k, v) VALUES($1, $2);
该方法
适用于罕见的大量数据传输。
从描述其数据结构的角度来看,临时表与
pg_class系统表中的“正常”功能只有一个功能不同,而在
pg_type,pg_depend,pg_attribute,pg_attrdef等方面则完全没有。
因此,在每个都有大量短期连接的Web系统中,这样的表每次都会生成新的系统记录,并在与数据库的连接关闭时将其删除。 结果,
对TEMP TABLE的
不受控制的使用导致pg_catalog中的表“膨胀”,并减慢了使用它们的许多操作。
当然,这可以借助
VACUUM FULL定期遍历系统目录表来解决。
会话变量
假设对于单个SQL查询,前一种情况下的数据处理非常复杂,但是您要经常这样做。 也就是说,我们希望在
DO块中使用过程处理,但是通过临时表使用数据传输将太昂贵。
我们也将无法使用$ n参数来转移到匿名块。 会话变量和
current_setting函数将帮助我们摆脱这种情况。
在9.2版之前,有必要为“您的”会话变量预先配置
custom_variable_classes 命名空间 。 在当前版本中,您可以编写如下内容:
SET my.val = '{1,2,3}'; DO $$ DECLARE id integer; BEGIN FOR id IN (SELECT unnest(current_setting('my.val')::integer[])) LOOP RAISE NOTICE 'id : %', id; END LOOP; END; $$ LANGUAGE plpgsql;
其他受支持的过程语言可以找到其他解决方案。
你知道更多方法吗? 分享评论!