使用Postgres hstore的竞争条件

我有一个表,其中包括许多其他领域的一个hstore

DB / schema.rb

 create_table "requests", force: true do |t| t.hstore "parameters" end 

一些记录具有字段parameters["company_id"]但不是全部。

我需要做的是确保只使用给定parameters["company_id"]创建一个Request对象。 可能有多次尝试同时保存记录 – 因此竞争条件。

我在整个表中的hstore寻找唯一的company_id值。

我发现我可以运行一个事务来锁定数据库并检查具有给定parameters["company_id"]的请求是否存在,如果不创建它。 如果company_idRequest模型上的一个简单字段,我可以这样做:

 Request.transaction do if Request.find_by(company_id: *id* ) log_duplication_attempt_and_quit else create_new_record log_successful_creation end end 

不幸的是它是hstore而我无法改变它。 使用hstore实现这一目标的最佳方法是什么?

我正在寻找快速的东西,因为表中有很多记录。 纯SQL查询没问题 – 遗憾的是我没有足够的SQL背景来弄明白我自己。 这可以为性能编制索引吗?

例:

 a = Request.new(parameters: {company_id: 567, name: "John"}) b = Request.new(parameters: {name: "Doesn't have company_id in the hstore"}) c = Request.new(parameters: {company_id: 567, name: "Galt"}) a.save // valid success b.save // valid success even if company_id hasn't been provided c.save // not valid Request with company_id 567 already in the table 

即使使用普通列,您的想法也不会保存以防止并发访问。 两个事务可能都会看到该值尚未同时存在并且尝试插入。

显然,为此目的使用专用的company_id会更清晰,然后一个简单的UNIQUE约束就能完成这项工作:

 ALTER TABLE requests ADD CONSTRAINT requests_company_id_uni UNIQUE (company_id); 

这样您就可以自动获得索引:

  • Postgres UNIQUE约束是否意味着索引?

你甚至可以将列作为外键引用…

通过拥有设置,您仍然可以使用function性UNIQUE索引

 CREATE UNIQUE INDEX requests_parameters_company_id_uni ON requests ((parameters->'company_id')); -- all parentheses required 

两种变体都允许多个NULL值,通常允许没有'company_id'键的条目。 您甚至可以使其成为部分的,function性的UNIQUE索引,以从索引中排除不相关的行(使索引更小):

 CREATE UNIQUE INDEX requests_parameters_company_id_uni ON requests ((parameters->'company_id')) WHERE (parameters->'company_id') IS NOT NULL; 

仅在没有company_id情况下才有用。

有关:

  • 复合PRIMARY KEY对涉及的列强制执行NOT NULL约束

SQL小提琴。

无论哪种方式,Postgres现在处理其余的事情。 尝试插入具有已存在的company_id (以某种方式)的行的任何事务都将引发唯一违规的exception并回滚整个事务。 始终保证唯一性。

如果要将作为重复项被拒绝的条目记录在一起,可以将INSERT封装在服务器端函数中,请捕获唯一违规并写入日志表:

您可以通过此搜索在SO 上找到示例。