在应用程序代码上使用 Postgres 的外部过程语言
Using Postgres' external procedural languages over application code
我正在尝试找出使用非 plpgsql 过程语言(PL/Python、PL/Perl、PL/v8 等)在数据库级别,而不是上升到与数据库交互的应用程序框架的模型 level/ORM(Rails、Entity Framework、Django 等)并在那里实现它。
举一个具体的例子,比方说,我有一个包含 Mustache 模板的 table,我想以某种方式“渲染”它们。
Table定义:
create table templates (
id serial primary key,
content text not null,
data jsonb not null
);
通常我会使用模型代码并添加额外的方法来呈现模板。 Rails 中的示例:
class Template < ApplicationRecord
def rendered
Mustache.render(content, data)
end
end
但是,我也可以编写一个 PL/Python 函数来完成此操作,但在数据库级别:
create or replace function fn_mustache(template text, data jsonb)
returns text
language plpython3u
as $$
import chevron
import json
return chevron.render(template, json.loads(data))
$$;
create view v_templates as
select id, content, data, fn_mustache(content, data) as rendered
from templates;
这在功能方面产生了几乎相同的结果。这个例子非常基础,但其想法是使用 PL/Python(或其他)以比 PL/pgsql 允许的更高级的方式操作数据。也就是说,PL/pgsql 没有今天任何通用编程语言提供的相同数量的库(在这个例子中,我依赖于 Mustache 模板系统的实现,这在 PL/pgsql 中实现是不切实际的案件)。我显然不会将 PL/Python 用于任何类型的网络或其他 OS 级别的功能,但对于专门针对数据的操作,这似乎是一种不错的方法(改变我的想法)。
目前我能观察到的要点:
- PL/Python 是一种“不受信任”的语言,我想根据定义,它使编写函数更加危险,因为您可以访问系统调用;至少感觉搞砸PL/Python函数的代价比应用层的错误要高,因为前者是在数据库上下文中执行的
- 数据库方法更具可扩展性,因为我在最接近数据的级别上工作,即我没有将表示逻辑分散到多个“层”(在本例中为 ORM 和 DB)。这意味着如果我需要一些其他有兴趣与数据交互的外部服务,我可以将它直接插入数据库,绕过应用层。
- 在模型级别上实现它似乎在执行中要简单得多
- 支持应用程序代码变体似乎也更容易,因为要记住的概念更少
这两种方法的其他优点和缺点是什么? (例如性能、可维护性)
您想知道是否在数据库中包含应用程序逻辑。这在很大程度上是一个品味问题。在过去,在数据库函数中实现应用程序逻辑的方法更为流行,但今天通常不受欢迎。
这场辩论中的极端立场是
应用程序是在数据库中实现的,以至于数据库函数会生成发送给客户端的 HTML 代码。
数据库只是表的愚蠢集合,除了主键之外没有触发器或约束,并且应用程序试图维护数据完整性。
最好的解决方案通常介于两者之间,但很大程度上取决于个人喜好。你看这是一个典型的基于意见的问题。但是,让我提供一些可以帮助您做出决定的论据。
反对数据库中应用程序逻辑的要点:
这使得移植到另一个数据库变得更加困难。
开发和调试数据库功能比客户端代码复杂。例如,您将没有高级调试工具。
该数据库机器不仅要执行正常的数据库工作负载,还要执行应用程序代码工作负载。但是数据库比应用程序服务器更难扩展(您不能只启动第二个数据库来处理部分工作负载)。
PostgreSQL-特定:所有数据库功能运行都在一个数据库事务中,因此您无法实现需要更复杂事务管理的功能。
数据库中的应用逻辑要点:
移植到另一个应用程序服务器或客户端编程语言变得更加容易。
客户端和服务器之间传输的数据更少,可以提高处理效率。
软件堆栈变短,整体软件架构更简单。
我个人的意见是,任何与基本数据完整性有关的事情都应该在数据库中实现:
数据库中有外键和检查约束。应用程序当然也会遵守这些规则(触发数据库错误没有意义),但拥有安全网对数据完整性有好处。
如果您必须在数据库中保留冗余信息,请使用触发器来确保数据的所有副本都保持同步。这隐含地利用了事务原子性。
任何更复杂的事情最好在应用程序中完成。警惕非常长或复杂的数据库函数。出于性能原因可能会有例外:也许一些复杂的报告不能轻易地用纯 SQL 编写,并且将所有原始数据发送给客户端的成本高得令人望而却步。
我正在尝试找出使用非 plpgsql 过程语言(PL/Python、PL/Perl、PL/v8 等)在数据库级别,而不是上升到与数据库交互的应用程序框架的模型 level/ORM(Rails、Entity Framework、Django 等)并在那里实现它。
举一个具体的例子,比方说,我有一个包含 Mustache 模板的 table,我想以某种方式“渲染”它们。 Table定义:
create table templates (
id serial primary key,
content text not null,
data jsonb not null
);
通常我会使用模型代码并添加额外的方法来呈现模板。 Rails 中的示例:
class Template < ApplicationRecord
def rendered
Mustache.render(content, data)
end
end
但是,我也可以编写一个 PL/Python 函数来完成此操作,但在数据库级别:
create or replace function fn_mustache(template text, data jsonb)
returns text
language plpython3u
as $$
import chevron
import json
return chevron.render(template, json.loads(data))
$$;
create view v_templates as
select id, content, data, fn_mustache(content, data) as rendered
from templates;
这在功能方面产生了几乎相同的结果。这个例子非常基础,但其想法是使用 PL/Python(或其他)以比 PL/pgsql 允许的更高级的方式操作数据。也就是说,PL/pgsql 没有今天任何通用编程语言提供的相同数量的库(在这个例子中,我依赖于 Mustache 模板系统的实现,这在 PL/pgsql 中实现是不切实际的案件)。我显然不会将 PL/Python 用于任何类型的网络或其他 OS 级别的功能,但对于专门针对数据的操作,这似乎是一种不错的方法(改变我的想法)。
目前我能观察到的要点:
- PL/Python 是一种“不受信任”的语言,我想根据定义,它使编写函数更加危险,因为您可以访问系统调用;至少感觉搞砸PL/Python函数的代价比应用层的错误要高,因为前者是在数据库上下文中执行的
- 数据库方法更具可扩展性,因为我在最接近数据的级别上工作,即我没有将表示逻辑分散到多个“层”(在本例中为 ORM 和 DB)。这意味着如果我需要一些其他有兴趣与数据交互的外部服务,我可以将它直接插入数据库,绕过应用层。
- 在模型级别上实现它似乎在执行中要简单得多
- 支持应用程序代码变体似乎也更容易,因为要记住的概念更少
这两种方法的其他优点和缺点是什么? (例如性能、可维护性)
您想知道是否在数据库中包含应用程序逻辑。这在很大程度上是一个品味问题。在过去,在数据库函数中实现应用程序逻辑的方法更为流行,但今天通常不受欢迎。
这场辩论中的极端立场是
应用程序是在数据库中实现的,以至于数据库函数会生成发送给客户端的 HTML 代码。
数据库只是表的愚蠢集合,除了主键之外没有触发器或约束,并且应用程序试图维护数据完整性。
最好的解决方案通常介于两者之间,但很大程度上取决于个人喜好。你看这是一个典型的基于意见的问题。但是,让我提供一些可以帮助您做出决定的论据。
反对数据库中应用程序逻辑的要点:
这使得移植到另一个数据库变得更加困难。
开发和调试数据库功能比客户端代码复杂。例如,您将没有高级调试工具。
该数据库机器不仅要执行正常的数据库工作负载,还要执行应用程序代码工作负载。但是数据库比应用程序服务器更难扩展(您不能只启动第二个数据库来处理部分工作负载)。
PostgreSQL-特定:所有数据库功能运行都在一个数据库事务中,因此您无法实现需要更复杂事务管理的功能。
数据库中的应用逻辑要点:
移植到另一个应用程序服务器或客户端编程语言变得更加容易。
客户端和服务器之间传输的数据更少,可以提高处理效率。
软件堆栈变短,整体软件架构更简单。
我个人的意见是,任何与基本数据完整性有关的事情都应该在数据库中实现:
数据库中有外键和检查约束。应用程序当然也会遵守这些规则(触发数据库错误没有意义),但拥有安全网对数据完整性有好处。
如果您必须在数据库中保留冗余信息,请使用触发器来确保数据的所有副本都保持同步。这隐含地利用了事务原子性。
任何更复杂的事情最好在应用程序中完成。警惕非常长或复杂的数据库函数。出于性能原因可能会有例外:也许一些复杂的报告不能轻易地用纯 SQL 编写,并且将所有原始数据发送给客户端的成本高得令人望而却步。