postgresql - 如何在 Postgres RDBMS 中为没有重叠和竞争条件的用户创建时间块条目(日历事件)
问题描述
我有一个表,其中包含 from 和 to 列,这些列是 Postgres DB 中带有时区的 DateTime 字段。该表还具有 user_id 以将此条目与用户相关联。
我想阻止特定用户的时间范围,当它可用时。
我目前的做法是
- 检查用户是否有任何行与我要阻止的时间重叠
- 如果未发现重叠,则为用户创建一个新行,其中包含在 from 和 to 字段中指定的时间块。
然而,
可能存在竞态条件,在第 1 步发生之后和第 2 步发生之前,另一个 API 请求可能会阻止时间重叠。
如何,
- 避免这种情况。
- 在 Postgres 中存储时间范围的最合适的数据结构和约束是什么,这样约束将确保不存在重叠用户的时间范围。(寻找复合唯一索引的作用)
- 另一个更好地处理这种情况的数据库?
解决方案
SQL 需要不同的思考过程。SQL 是声明性的,并且基于集合操作而不是过程。但是您当前的方法是程序性的(第一次这样做,然后如果成功就这样做)。基于集合的视图将其减少到仅 1 步(如果不是,请执行此操作)。您的具体情况变为:insert a time block for the user when that time block does not overlap an existing one for the user
以下以一种方式来完成此操作。
假设此功能对您来说很常见,下面将构建一个 SQL 函数来完成它。如果请求的时间范围与它返回的用户的现有时间范围重叠,否则它返回插入的行。(您没有提供表定义,所以我只是做了一个)。
create table user_cal ( id integer generated always as identity
, user_id integer
, start_time timestamptz
, end_time timestamptz
) ;
create or replace
function gen_user_cal(user_id_in user_cal.user_id%type
,start_time_in user_cal.start_time%type
,end_time_in user_cal.end_time%type
)
returns user_cal
language sql
as $$
insert into user_cal(user_id, start_time, end_time)
select user_id_in,start_time_in,end_time_in
where not exists
( select null
from user_cal uc
where uc.user_id = user_id_in
and tstzrange(start_time_in,end_time_in,'[]') &&
tstzrange(uc.start_time,uc.end_time,'[]')
)
returning *
$$;
它通过创建一个 TSTZRANGE(请参阅范围类型和范围运算符)来工作,其中包括请求和现有期间的开始时间和结束时间。然后检查它们是否重叠(范围重叠运算符(&&))。请参见此处的示例。
这大大降低了达到比赛条件的机会。但并没有完全消除它。如果仍然担心,请参阅 Transaction Isolation Serializable。
推荐阅读
- c - 从 GNU AS 调用时,C 乘法结果放在哪里?
- python - 如何在 SQLAlchemy 和 Firebird 的自定义查询中将 Python 列表绑定为参数?
- php - 将提交的表单值保存为 cookie
- java - 升级 Eclipse 时快速远程重新安装所需的插件
- authentication - WSO2 APIM 身份在 SAML SSO 响应的 NameID 中回复用户名或域/用户名
- angular - 如何在 ionic4 应用程序中使用原生谷歌地图?
- .net - 查找大数组的声明
- facebook-graph-api - Instagram:无法通过 ID 获取标记的媒体对象
- python - 如何将类实例分配给一个变量并在其他类中使用它
- pattern-matching - 案例表达式和“重载”参数计数