使用Postgres hstore的竞争条件

时间:2015-09-23 13:16:21

标签: ruby-on-rails postgresql database-design hstore unique-index

我有一个表格,其中包含许多其他字段hstore

分贝/ schema.rb

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

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

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

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

我发现我可以运行一个事务来锁定数据库并检查给定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

1 个答案:

答案 0 :(得分:1)

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

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

ALTER TABLE requests ADD CONSTRAINT requests_company_id_uni UNIQUE (company_id);

这样您就可以自动

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

使用设置 ,您仍然可以使用functional UNIQUE index

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

两种变体都允许多个NULL值,通常允许不带'company_id'键的条目。您甚至可以将其设为partial, functional UNIQUE index以从索引中排除不相关的行(使索引更小):

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

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

相关:

SQL Fiddle.

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

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

您将在SO with this search上找到示例。