如果值不存在则插入新行并以任一方式获取id

时间:2012-11-09 14:12:25

标签: sql performance postgresql concurrency sql-insert

我想在表中插入一条记录,如果记录已经存在则获取其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;

如何优化这样的案例?

1 个答案:

答案 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)
       );
  • CTE x仅为方便起见:输入一次值。
  • CTE y检索tbl_id - 如果它已经存在。
  • CTE z会插入新行 - 如果没有。
  • 最终SELECT避免在使用COALESCE构造的表格上运行另一个查询。

现在,如果并发事务在CTE yz之间提交some_col ='foo'的新行,则仍然会失败,但这种情况极不可能。如果发生这种情况,您将获得重复的密钥违规,并且必须重试。什么都没有丢失。如果你没有面对并发写,你可以忘记这一点。

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

不言而喻,您需要在此设置中使用两个索引(如上面的CREATE TABLE语句中所示):

  • UNIQUE上的PRIMARY KEYtbl_id约束(serial类型!)
  • UNIQUE
  • 上的另一个PRIMARY KEYsome_col约束

两者都自动实现索引。