运行 查询 Postgres 中的所有模式
Running query against all schemas in Postgres
上下文
我们在 Postgres 数据库中进行基于模式的多租户。每个模式都与不同的租户相关联,并且将具有完全相同的结构 除了一个名为 public
.
的模式
要获取所有相关模式的列表,我们可以使用:
SELECT tenant_schema FROM public.tenant_schema_mappings;
问题
我们需要为所有租户定期清理特定的table:
DELETE FROM a_tenant_schema.parent WHERE expiration_date_time < NOW();
(实际查询有点复杂,因为我们还需要删除与 parent
关联的链接 children
条目,但为了这个问题,让我们保持简单。 )
约束
- 我们无法使用
pg_cron
,因为我们的数据库服务器托管在 Azure 上,目前尚不支持该扩展。
- 我们不想部署整个 service/application 只是为了执行 cron 作业。
- 因此,我们决定使用部署在我们的 k8s 命名空间中的
CronJob
pod,因此可以使用 psql
客户端通过 shell 命令直接与数据库通信。
问题
在 shell 中使用 psql
针对所有相关模式执行给定 DELETE
语句的最佳方法是什么?
请记住:由于可能有数百个租户,运行 并行清理每个租户的查询可能很有趣。
当前可能的解决方案
到目前为止,似乎主要有两种有趣的方法(尽管我不太确定如何并行执行查询):
- 弄清楚如何在单个存储过程中执行所有操作,然后使用
psql -c
. 简单地调用该 SP
- 使用
psql -c "SELECT tenant_schema FROM public.tenant_schema_mappings;"
收集所有相关租户模式的列表,然后使用 shell 命令通过动态构造适当的查询来遍历该列表。使用查询的结果集,运行 一个一个地使用 psql -c
。
其他部分解决方案
我认为我们实际上可以使用以下 SQL:
构建查询
SELECT 'DELETE * FROM ' || tenant_schema || '.parent WHERE expiration_date_time < NOW();' AS query
FROM public.tenant_schema_mappings;
也许有办法告诉 Postgres 执行所有结果字符串?
您可以定义一个使用 dynamic commands, 的 Postgres 过程,例如:
create or replace procedure clear_tenants()
language plpgsql as $function$
declare
tenant text;
begin
for tenant in
select tenant_schema
from public.tenant_schema_mappings
loop
execute format($ex$
delete from %I.parent
where expiration_date_time < now()
$ex$, tenant);
end loop;
end
$function$;
那么您的 cron 任务所要做的就是调用过程:
call clear_tenants()
在 Postgres 10 或更早版本中,使用函数或 do 块代替过程。
这个简单解决方案的主要缺点是所有内容都在单个事务中执行。遗憾的是,您无法控制包含动态查询的过程中的事务。我会在 table 描述模式中定义 chunk_number
并在其自己的事务中为每个块调用该过程。
create or replace procedure public.clear_tenants(chunk integer)
language plpgsql as $function$
declare
tenant text;
begin
for tenant in
select tenant_schema
from public.tenant_schema_mappings
where chunk_number = chunk
loop
execute format($ex$
delete from %I.parent
where expiration_date_time < now()
$ex$, tenant);
end loop;
end
$function$;
在客户端,我必须准备一个格式如下的脚本:
-- in psql the procedures will be executed in separate transactions
-- if you do not use begin; explicitly
call clear_tenants(1);
call clear_tenants(2);
call clear_tenants(3);
...
或者为单个块执行许多 psql 实例(每个块都在一个单独的连接中)。最后一个选项实际上是强制并发的唯一方法。当然,它受到合理的并发连接数的限制。
以下函数发出通知,其中包含每个租户的已删除行数和 returns 块的已删除行总数:
create or replace function public.clear_tenants_modified(chunk integer)
returns bigint language plpgsql as $function$
declare
tenant text;
deleted_rows bigint;
total_deleted_rows bigint = 0;
begin
for tenant in
select tenant_schema
from public.tenant_schema_mappings
where chunk_number = chunk
loop
execute format($ex$
with delete_statement as (
delete from %I.parent
where expiration_date_time < now()
returning 1 as x)
select count(x)
from delete_statement
$ex$, tenant)
into deleted_rows;
raise notice '%: %', tenant, deleted_rows;
total_deleted_rows = total_deleted_rows+ deleted_rows;
end loop;
return total_deleted_rows;
end
$function$;
select clear_tenants_modified(1);
上下文
我们在 Postgres 数据库中进行基于模式的多租户。每个模式都与不同的租户相关联,并且将具有完全相同的结构 除了一个名为 public
.
要获取所有相关模式的列表,我们可以使用:
SELECT tenant_schema FROM public.tenant_schema_mappings;
问题
我们需要为所有租户定期清理特定的table:
DELETE FROM a_tenant_schema.parent WHERE expiration_date_time < NOW();
(实际查询有点复杂,因为我们还需要删除与 parent
关联的链接 children
条目,但为了这个问题,让我们保持简单。 )
约束
- 我们无法使用
pg_cron
,因为我们的数据库服务器托管在 Azure 上,目前尚不支持该扩展。 - 我们不想部署整个 service/application 只是为了执行 cron 作业。
- 因此,我们决定使用部署在我们的 k8s 命名空间中的
CronJob
pod,因此可以使用psql
客户端通过 shell 命令直接与数据库通信。
问题
在 shell 中使用 psql
针对所有相关模式执行给定 DELETE
语句的最佳方法是什么?
请记住:由于可能有数百个租户,运行 并行清理每个租户的查询可能很有趣。
当前可能的解决方案
到目前为止,似乎主要有两种有趣的方法(尽管我不太确定如何并行执行查询):
- 弄清楚如何在单个存储过程中执行所有操作,然后使用
psql -c
. 简单地调用该 SP
- 使用
psql -c "SELECT tenant_schema FROM public.tenant_schema_mappings;"
收集所有相关租户模式的列表,然后使用 shell 命令通过动态构造适当的查询来遍历该列表。使用查询的结果集,运行 一个一个地使用psql -c
。
其他部分解决方案
我认为我们实际上可以使用以下 SQL:
构建查询SELECT 'DELETE * FROM ' || tenant_schema || '.parent WHERE expiration_date_time < NOW();' AS query
FROM public.tenant_schema_mappings;
也许有办法告诉 Postgres 执行所有结果字符串?
您可以定义一个使用 dynamic commands, 的 Postgres 过程,例如:
create or replace procedure clear_tenants()
language plpgsql as $function$
declare
tenant text;
begin
for tenant in
select tenant_schema
from public.tenant_schema_mappings
loop
execute format($ex$
delete from %I.parent
where expiration_date_time < now()
$ex$, tenant);
end loop;
end
$function$;
那么您的 cron 任务所要做的就是调用过程:
call clear_tenants()
在 Postgres 10 或更早版本中,使用函数或 do 块代替过程。
这个简单解决方案的主要缺点是所有内容都在单个事务中执行。遗憾的是,您无法控制包含动态查询的过程中的事务。我会在 table 描述模式中定义 chunk_number
并在其自己的事务中为每个块调用该过程。
create or replace procedure public.clear_tenants(chunk integer)
language plpgsql as $function$
declare
tenant text;
begin
for tenant in
select tenant_schema
from public.tenant_schema_mappings
where chunk_number = chunk
loop
execute format($ex$
delete from %I.parent
where expiration_date_time < now()
$ex$, tenant);
end loop;
end
$function$;
在客户端,我必须准备一个格式如下的脚本:
-- in psql the procedures will be executed in separate transactions
-- if you do not use begin; explicitly
call clear_tenants(1);
call clear_tenants(2);
call clear_tenants(3);
...
或者为单个块执行许多 psql 实例(每个块都在一个单独的连接中)。最后一个选项实际上是强制并发的唯一方法。当然,它受到合理的并发连接数的限制。
以下函数发出通知,其中包含每个租户的已删除行数和 returns 块的已删除行总数:
create or replace function public.clear_tenants_modified(chunk integer)
returns bigint language plpgsql as $function$
declare
tenant text;
deleted_rows bigint;
total_deleted_rows bigint = 0;
begin
for tenant in
select tenant_schema
from public.tenant_schema_mappings
where chunk_number = chunk
loop
execute format($ex$
with delete_statement as (
delete from %I.parent
where expiration_date_time < now()
returning 1 as x)
select count(x)
from delete_statement
$ex$, tenant)
into deleted_rows;
raise notice '%: %', tenant, deleted_rows;
total_deleted_rows = total_deleted_rows+ deleted_rows;
end loop;
return total_deleted_rows;
end
$function$;
select clear_tenants_modified(1);