Kev*_*rke 7 postgresql index constraint
假设我有一个像这样的数据库模式
user_id text
photo_id text
date_created timestamp
Run Code Online (Sandbox Code Playgroud)
我想强制执行“一个用户只能拥有 30 张照片”的约束。实现这一点的一个天真的方法是
SELECT COUNT(*) FROM user_photos WHERE user_id='usr_123'
Run Code Online (Sandbox Code Playgroud)
将结果与 30 进行比较,如果较低,则进行插入。天真,因为如果您可以在短时间内触发多个请求,您就可以赢得一场比赛并最终写入 30 多个记录。
有没有办法在数据库中强制执行此约束?例如创建某种索引“只能插入 30 行”,或者什么?有没有办法让一个count
列为每个 userId 单独递增,并且可以将其范围限制为0-29
?
如果您使用事务,会锁定您读取的行吗?
类似于@Eelke和@Kassandry的建议。但您不需要计数器或触发器。
CREATE TABLE t1 (
user_id text NOT NULL -- should probably be integer
, photo_nr int NOT NULL
, photo_id text NOT NULL
, date_created timestamptz NOT NULL DEFAULT now()
, CONSTRAINT t1_pkey PRIMARY KEY (user_id, photo_nr)
, CONSTRAINT max_30_photos CHECK (photo_nr BETWEEN 1 AND 30)
);
Run Code Online (Sandbox Code Playgroud)
关键问题是如何廉价且安全地获得下一个空闲时段。我建议:
SELECT photo_nr
FROM generate_series (1,30) photo_nr
EXCEPT (SELECT photo_nr FROM t1 WHERE user_id = 'usr123') -- your user_id here
ORDER BY 1
LIMIT 1;
Run Code Online (Sandbox Code Playgroud)
细节:
photo_nr
这将返回给定用户的下一个空闲时间。您的INSERT
陈述以此为基础:
INSERT INTO t1 (user_id, photo_nr, photo_id)
SELECT 'usr123', photo_nr, 'my_new_photo_id' -- provide values here
FROM (
SELECT photo_nr
FROM generate_series (1,30) photo_nr
EXCEPT (SELECT photo_nr FROM t1 WHERE user_id = 'usr123') -- given user_id
ORDER BY 1
LIMIT 1
) sub;
Run Code Online (Sandbox Code Playgroud)
如果没有空闲插槽,则不会插入任何内容(如命令标签所报告)。
这对于并发请求来说并不安全。对于所有并发事务,下一次释放photo_nr
都是相同的。它可能会导致并发的独特违规INSERT
。但在那些非常罕见的情况下,捕获该错误并重试通常比锁定整个表(无法锁定 Postgres 中尚不存在的行)或独占锁定给定用户的所有行要便宜。
您可以将 包装INSERT
在 PL/pgSQL 函数中以重试唯一的违规行为。代码示例:
如果这是一个问题,一个廉价的替代方案是锁定表中的父行users
以排除任何竞争条件。看:
小智 4
您的程序可以手动为每个用户的记录编号,然后您可以对 user_id 和 recordnumber 的组合施加唯一约束(或使其成为主键),并对 recordnumber 添加检查约束,检查其是否在 1 到 30 之间。
归档时间: |
|
查看次数: |
6005 次 |
最近记录: |