保证同一项目中没有两个用户在user_id
并发插入时被分配相同的直接方法是防止并发活动。
实现它的一种方法是将事务隔离级别设置为可序列化。
BEGIN TRANSACTION
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
-- here I took the query from George Joseph's answer
insert into user_table
(user_id, project_id)
select
coalesce(max(user_id), 0) + 1 as user_id
,@project_id as project_id
from
user_table
where
project_id=@project_id
COMMIT TRANSACTION
您可以同时从多个会话运行此查询块,引擎将在后台处理并发。我真的不知道 Postgres 是如何做到的。最有可能的并发事务将等待前一个完成。
为了有效地工作,您需要一个关于(project_id, user_id)
. 您还需要使其唯一以强制执行您的约束。此索引中列的顺序很重要。
您还提到您预计会有数千个项目,最终每个项目会有数百万用户。这加起来有 10 亿行,这MAX
对于每次插入来说都是相当多的。即使有适当的索引。
您可以创建一个单独的表project_sequences
来存储user_id
每个的最后一个值project_id
。该表将有两列project_id
,并且两列last_user_id
都有主键(project_id, last_user_id)
。索引中列的顺序很重要。
现在,您可以project_sequences
在主大表中的每次插入中查询和更新只有 1000 行的小表。我不熟悉变量的 Postgres 语法,所以下面是伪代码。
BEGIN TRANSACTION
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
-- read the last_user_id for the given project_id from the small table into a variable
-- and increment it
-- update the small table with the new last_user_id
-- use the freshly generated user_id to insert into the main table
-- or, without variables
-- increment the last_user_id
update project_sequences
set last_user_id =
(
select coalesce(max(last_user_id), 0) + 1
from project_sequences
where project_id=@project_id
)
where
project_id=@project_id
-- use the new id to insert into the main table
insert into user_table
(user_id, project_id)
select
last_user_id
,@project_id as project_id
from
project_sequences
where
project_id=@project_id
COMMIT TRANSACTION
project_id
使用变量,当给定的是新的,表中尚不存在并将新的设置user_id
为从 1 或您需要的任何起始值开始时,处理这种情况会更容易一些。