Return 行来自 INSERT with ON CONFLICT 而无需更新
Return rows from INSERT with ON CONFLICT without needing to update
我经常需要从具有唯一约束的 table 中获取一行,如果 none 存在,则创建它并 return。
例如我的 table 可能是:
CREATE TABLE names(
id SERIAL PRIMARY KEY,
name TEXT,
CONSTRAINT names_name_key UNIQUE (name)
);
它包含:
id | name
1 | bob
2 | alice
那我想:
INSERT INTO names(name) VALUES ('bob')
ON CONFLICT DO NOTHING RETURNING id;
或者也许:
INSERT INTO names(name) VALUES ('bob')
ON CONFLICT (name) DO NOTHING RETURNING id
并拥有 return 鲍勃的 ID 1
。但是,RETURNING
只有 return 插入或更新的行。所以,在上面的例子中,它不会 return 任何东西。为了让它按预期运行,我实际上需要:
INSERT INTO names(name) VALUES ('bob')
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE
SET name = 'bob'
RETURNING id;
这看起来有点麻烦。我想我的问题是:
不允许(我的)期望行为的原因是什么?
有更优雅的方法吗?
这是 SELECT or INSERT
的反复出现的问题,与 UPSERT 相关(但不同)。 Postgres 9.5 中的新 UPSERT 功能仍然有用。
WITH ins AS (
INSERT INTO names(name)
VALUES ('bob')
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE
SET name = NULL
WHERE FALSE -- never executed, but locks the row
RETURNING id
)
SELECT id FROM ins
UNION ALL
SELECT id FROM names
WHERE name = 'bob' -- only executed if no INSERT
LIMIT 1;
这样你实际上不需要编写新的行版本。
我假设您知道在 Postgres 中每个 UPDATE
都会写一个新版本的行,因为它的 MVCC model - 即使 name
设置为与以前相同的值。这将使操作更加昂贵,在某些情况下增加可能的并发问题/锁争用并使 table 额外膨胀。
但是,竞争条件仍然存在微小的极端情况。并发事务可能添加了一个冲突行,该行在同一语句中尚不可见。然后INSERT
和SELECT
空出来
单行UPSERT的正确解决方案:
- Is SELECT or INSERT in a function prone to race conditions?
批量 UPSERT 的一般解决方案:
没有并发写入负载
如果并发写入(来自不同的会话)是不可能的,则不需要锁定该行并可以简化:
WITH ins AS (
INSERT INTO names(name)
VALUES ('bob')
ON CONFLICT ON CONSTRAINT names_name_key DO NOTHING -- no lock needed
RETURNING id
)
SELECT id FROM ins
UNION ALL
SELECT id FROM names
WHERE name = 'bob' -- only executed if no INSERT
LIMIT 1;
我经常需要从具有唯一约束的 table 中获取一行,如果 none 存在,则创建它并 return。 例如我的 table 可能是:
CREATE TABLE names(
id SERIAL PRIMARY KEY,
name TEXT,
CONSTRAINT names_name_key UNIQUE (name)
);
它包含:
id | name
1 | bob
2 | alice
那我想:
INSERT INTO names(name) VALUES ('bob')
ON CONFLICT DO NOTHING RETURNING id;
或者也许:
INSERT INTO names(name) VALUES ('bob')
ON CONFLICT (name) DO NOTHING RETURNING id
并拥有 return 鲍勃的 ID 1
。但是,RETURNING
只有 return 插入或更新的行。所以,在上面的例子中,它不会 return 任何东西。为了让它按预期运行,我实际上需要:
INSERT INTO names(name) VALUES ('bob')
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE
SET name = 'bob'
RETURNING id;
这看起来有点麻烦。我想我的问题是:
不允许(我的)期望行为的原因是什么?
有更优雅的方法吗?
这是 SELECT or INSERT
的反复出现的问题,与 UPSERT 相关(但不同)。 Postgres 9.5 中的新 UPSERT 功能仍然有用。
WITH ins AS (
INSERT INTO names(name)
VALUES ('bob')
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE
SET name = NULL
WHERE FALSE -- never executed, but locks the row
RETURNING id
)
SELECT id FROM ins
UNION ALL
SELECT id FROM names
WHERE name = 'bob' -- only executed if no INSERT
LIMIT 1;
这样你实际上不需要编写新的行版本。
我假设您知道在 Postgres 中每个 UPDATE
都会写一个新版本的行,因为它的 MVCC model - 即使 name
设置为与以前相同的值。这将使操作更加昂贵,在某些情况下增加可能的并发问题/锁争用并使 table 额外膨胀。
但是,竞争条件仍然存在微小的极端情况。并发事务可能添加了一个冲突行,该行在同一语句中尚不可见。然后INSERT
和SELECT
空出来
单行UPSERT的正确解决方案:
- Is SELECT or INSERT in a function prone to race conditions?
批量 UPSERT 的一般解决方案:
没有并发写入负载
如果并发写入(来自不同的会话)是不可能的,则不需要锁定该行并可以简化:
WITH ins AS (
INSERT INTO names(name)
VALUES ('bob')
ON CONFLICT ON CONSTRAINT names_name_key DO NOTHING -- no lock needed
RETURNING id
)
SELECT id FROM ins
UNION ALL
SELECT id FROM names
WHERE name = 'bob' -- only executed if no INSERT
LIMIT 1;