3

我想向表中插入一条记录,如果该记录已经存在,则获取其 ID,否则运行插入并获取新记录的 ID。

我将插入数百万条记录,但不知道如何以有效的方式执行此操作。我现在正在做的是运行一个选择来检查记录是否已经存在,如果没有,则插入它并获取插入记录的 id。随着桌子越来越大,我想那SELECT会杀了我。

我现在用 psycopg2 在 python 中做的事情是这样的:

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;

关于如何优化这样的案例的任何想法?

4

1 回答 1

2

首先,这显然不是从未提及UPSERT的。UPDATE不过,类似的并发问题也适用。

这种任务总会有竞争条件,但您可以将其最小化到一个非常小的时间段,同时使用数据修改 CTE(PostgreSQL 9.1 引入)只查询一次ID:

给定一张桌子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)
       );
  • CTEx只是为了方便:输入一次值。
  • CTEy检索 tbl_id - 如果它已经存在。
  • CTEz插入新行 - 如果没有。
  • 最后避免使用该构造SELECT在表上运行另一个查询。COALESCE

y现在,如果并发事务提交了一个恰好在 CTE和之间的 some_col = 'foo' 的新行,这仍然会失败z,但这极不可能。如果发生这种情况,您会遇到重复的密钥违规,并且必须重试。什么都没有丢失。如果您不面对并发写入,您可以忘记这一点。

您可以将其放入 plpgsql 函数并自动重新运行重复键错误的查询。

不用说,在此设置中您需要两个索引(如我CREATE TABLE上面的声明中所示):

  • 一个UNIQUEPRIMARY KEY约束tbl_id(这是serial类型!)
  • 另一个UNIQUEPRIMARY KEY约束some_col

两者都自动实现索引。

于 2012-11-09T19:12:02.327 回答