我想在表中插入一条记录,如果记录已经存在则获取其id,否则运行insert并获取新记录的id。
我将插入数百万条记录,并且不知道如何以有效的方式执行此操作。我现在正在做的是运行一个select以检查记录是否已经存在,如果没有,则插入它并获取插入记录的id。随着桌子的增长,我想SELECT
会杀了我。
我现在在python中使用psycopg2做的事情如下:
select = ("SELECT id FROM ... WHERE ...", [...])
cur.execute(*select)
if not cur.rowcount:
insert = ("INSERT INTO ... VALUES ... RETURNING id", [...])
cur.execute(*insert)
rid = cur.fetchone()[0]
是否可以在这样的存储过程中执行某些操作:
BEGIN
EXECUTE sql_insert;
RETURN id;
EXCEPTION WHEN unique_violation THEN
-- return id of already existing record
-- from the exception info ?
END;
如何优化这样的案例?
答案 0 :(得分:2)
首先,显然不 UPSERT
因为UPDATE
从未被提及过。但是,类似的并发问题也适用。
此类任务总会存在竞争条件,但您可以将其最小化到极小的时间段,同时仅使用数据查询ID 一次修改CTE(PostgreSQL 9.1引入):
给出一个表tbl
:
CREATE TABLE tbl(tbl_id serial PRIMARY KEY, some_col text UNIQUE);
使用此查询:
WITH x AS (SELECT 'baz'::text AS some_col) -- enter value(s) once
, y AS (
SELECT x.some_col
, (SELECT t.tbl_id FROM tbl t WHERE t.some_col = x.some_col) AS tbl_id
FROM x
)
, z AS (
INSERT INTO tbl(some_col)
SELECT y.some_col
FROM y
WHERE y.tbl_id IS NULL
RETURNING tbl_id
)
SELECT COALESCE(
(SELECT tbl_id FROM z)
,(SELECT tbl_id FROM y)
);
x
仅为方便起见:输入一次值。y
检索tbl_id - 如果它已经存在。z
会插入新行 - 如果没有。SELECT
避免在使用COALESCE
构造的表格上运行另一个查询。现在,如果并发事务在CTE y
和z
之间提交some_col ='foo'的新行,则仍然会失败,但这种情况极不可能。如果发生这种情况,您将获得重复的密钥违规,并且必须重试。什么都没有丢失。如果你没有面对并发写,你可以忘记这一点。
您可以将其放入plpgsql函数中,并自动重新运行查询重复键错误。
不言而喻,您需要在此设置中使用两个索引(如上面的CREATE TABLE
语句中所示):
UNIQUE
上的PRIMARY KEY
或tbl_id
约束(serial
类型!)UNIQUE
PRIMARY KEY
或some_col
约束
两者都自动实现索引。