如何提高 postgreSQL 中批量 UPSERT 的速度?
How do I increase the speed of a bulk UPSERT in postgreSQL?
我正在尝试使用以下设计将数百万条数据记录从多个不同的来源加载到 postgresql table:
CREATE TABLE public.variant_fact (
variant_id bigint NOT NULL,
ref_allele text NOT NULL,
allele text NOT NULL,
variant_name text NOT NULL,
start bigint,
stop bigint,
variant_attributes jsonb
);
ALTER TABLE public.variant_fact
ADD CONSTRAINT variant_fact_unique UNIQUE (variant_name, start, stop, allele, ref_allele)
INCLUDE (ref_allele, allele, variant_name, start, stop);
其中“start”和“stop”是外键,“variant_id”是自增主键。我 运行 遇到加载速度问题,因为为了执行 UPSERT,我需要检查 table 以查看我上传的每个元素是否存在一个元素。我正在使用 psycopg2 使用 execute_values 方法在 python 中执行操作。
insert_query = """
INSERT INTO variant_fact AS v (variant_id, ref_allele, allele, variant_name, start, stop, variant_attributes)
VALUES %s
ON CONFLICT ON CONSTRAINT variant_fact_unique DO UPDATE
SET variant_attributes = excluded.variant_attributes || v.variant_attributes
RETURNING variant_id;
"""
inserted = psycopg2.extras.execute_values(cur=cursor, sql=sql, argslist=argslist, template=None, page_size=50000, fetch=fetch)
在我的例子中,argslist 是要插入数据库的元组列表。我试图利用这个 python 脚本来提高速度,但是这个 UPSERT 块的性能不是很好。在不同的模式之外(可能没有原子元素记录),是否有任何方法可以提高上传性能?我已经为 table 关闭了 WAL,并删除了“start”和“stop”的外键约束。我在这里遗漏了什么明显的东西吗?
关闭WAL(设置table UNLOGGED
)意味着table在崩溃后将是空的,因为它无法恢复。如果你正在考虑以后 运行 ALTER TABLE
将其更改为 LOGGED
table,请知道此操作会将整个 table 转储到 WAL 中,因此你赢了'赢得任何东西。
对于未记录的 table 上的简单语句,加快速度的唯一方法是:
删除除 variant_fact_unique
之外的所有索引、触发器和约束——但重新创建它们会很昂贵,因此您可能无法赢得整体
确保您有快速存储和足够的 RAM
按“variant_name”和“开始”(索引中的前两列)对 arglist 进行排序应该确保大多数索引查找将命中已缓存的页面。让 table 也聚集在该索引上将有助于确保 table 页面也以缓存友好的方式访问(尽管它在面对新数据时不会很好地保持聚集)。
此外,您的索引大小无端地增加了一倍。对已经是索引主要部分的列执行 INCLUDE 是没有意义的。这将花费您 CPU 和 IO 来格式化和写入数据(和 WAL),并且还会减少适合缓存的数据量。
我正在尝试使用以下设计将数百万条数据记录从多个不同的来源加载到 postgresql table:
CREATE TABLE public.variant_fact (
variant_id bigint NOT NULL,
ref_allele text NOT NULL,
allele text NOT NULL,
variant_name text NOT NULL,
start bigint,
stop bigint,
variant_attributes jsonb
);
ALTER TABLE public.variant_fact
ADD CONSTRAINT variant_fact_unique UNIQUE (variant_name, start, stop, allele, ref_allele)
INCLUDE (ref_allele, allele, variant_name, start, stop);
其中“start”和“stop”是外键,“variant_id”是自增主键。我 运行 遇到加载速度问题,因为为了执行 UPSERT,我需要检查 table 以查看我上传的每个元素是否存在一个元素。我正在使用 psycopg2 使用 execute_values 方法在 python 中执行操作。
insert_query = """
INSERT INTO variant_fact AS v (variant_id, ref_allele, allele, variant_name, start, stop, variant_attributes)
VALUES %s
ON CONFLICT ON CONSTRAINT variant_fact_unique DO UPDATE
SET variant_attributes = excluded.variant_attributes || v.variant_attributes
RETURNING variant_id;
"""
inserted = psycopg2.extras.execute_values(cur=cursor, sql=sql, argslist=argslist, template=None, page_size=50000, fetch=fetch)
在我的例子中,argslist 是要插入数据库的元组列表。我试图利用这个 python 脚本来提高速度,但是这个 UPSERT 块的性能不是很好。在不同的模式之外(可能没有原子元素记录),是否有任何方法可以提高上传性能?我已经为 table 关闭了 WAL,并删除了“start”和“stop”的外键约束。我在这里遗漏了什么明显的东西吗?
关闭WAL(设置table UNLOGGED
)意味着table在崩溃后将是空的,因为它无法恢复。如果你正在考虑以后 运行 ALTER TABLE
将其更改为 LOGGED
table,请知道此操作会将整个 table 转储到 WAL 中,因此你赢了'赢得任何东西。
对于未记录的 table 上的简单语句,加快速度的唯一方法是:
删除除
variant_fact_unique
之外的所有索引、触发器和约束——但重新创建它们会很昂贵,因此您可能无法赢得整体确保您有快速存储和足够的 RAM
按“variant_name”和“开始”(索引中的前两列)对 arglist 进行排序应该确保大多数索引查找将命中已缓存的页面。让 table 也聚集在该索引上将有助于确保 table 页面也以缓存友好的方式访问(尽管它在面对新数据时不会很好地保持聚集)。
此外,您的索引大小无端地增加了一倍。对已经是索引主要部分的列执行 INCLUDE 是没有意义的。这将花费您 CPU 和 IO 来格式化和写入数据(和 WAL),并且还会减少适合缓存的数据量。