简体   繁体   English

使用Postgres hstore的比赛条件

[英]Race condition using Postgres hstore

I have a table which has amongst many other fields one hstore 我有有以及许多其他领域的一个表hstore

db/schema.rb DB / schema.rb

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

Some of the records have a field parameters["company_id"] but not all of them. 一些记录具有字段parameters["company_id"]但不是全部。

What I need to do is to make sure that only one Request object is created with a given parameters["company_id"] . 我需要做的是确保仅使用给定的parameters["company_id"]创建一个Request对象。 There might be multiple attempts trying to save a record at the same time - thus the race condition. 可能有多次尝试尝试同时保存一条记录-因此出现了竞争状况。

I am looking for unique company_id values inside the hstore across the table. 我在整个表的hstore寻找唯一的company_id值。

I figure out that I could run a transaction to lock the database and check if the request with given parameters["company_id"] exist end if not create it. 我发现我可以运行一个事务来锁定数据库,并检查如果没有创建给定parameters["company_id"]的请求是否结束。 If company_id would be a simple field on a Request model I could do something like this: 如果company_id将是Request模型上的一个简单字段,则可以执行以下操作:

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

Unfortunately it is hstore and I can't change it. 不幸的是它是hstore ,我无法更改。 What would be the best way to achieve this with hstore ? hstore实现此目标的最佳方法是什么?

I am looking for something fast as there are a lot of records in a table. 我正在寻找快速的东西,因为表中有很多记录。 Pure SQL query is OK - unfortunately I don't have enough SQL background to figure it out my self. 纯粹的SQL查询是可以的-不幸的是,我没有足够的SQL背景来弄清楚自己。 Can this be indexed for performance? 可以为性能编制索引吗?

Example: 例:

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

Your idea would not be save against concurrent access, even with a plain column. 即使使用简单的列,您的想法也不会针对并发访问而保存。 Two transaction might both see that the value is not there yet at the same time and both try to insert. 两个事务可能都同时看到该值不存在,并且尝试插入。

Obviously it would be cleaner to have a dedicated company_id column for the purpose, then a plain UNIQUE constraint would do the job: 显然,为此目的使用专用的company_id会更清洁,然后使用普通的UNIQUE约束即可:

ALTER TABLE requests ADD CONSTRAINT requests_company_id_uni UNIQUE (company_id);

This way you have an index automatically : 这样,您将自动拥有一个索引:

And you could even reference the column as foreign key ... 您甚至可以将该列引用为外键...

With the setup you have you can still make it work with a functional UNIQUE index : 通过设置你你仍然可以把它与工作功能UNIQUE索引

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

Both variants allow multiple NULL values, entries without the 'company_id' key are generally allowed. 两种变体都允许多个NULL值,通常允许使用不带'company_id'键的条目。 You could even make it a partial, functional UNIQUE index to exclude irrelevant rows from the index (makes the index smaller): 您甚至可以使其成为部分功能性UNIQUE索引,以从索引中排除无关的行(使索引变小):

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

Only useful if you have more than a few without company_id . 仅在不使用company_id情况下,才有用。

Related: 有关:

SQL Fiddle. SQL提琴。

Either way, Postgres now handles the rest. 无论哪种方式,Postgres现在都能处理其余的工作。 Any transaction trying to insert a row with a company_id that's already present (one way or the other) will raise an exception for the unique violation and roll back the whole transaction. 任何试图插入具有company_id的行(一种方式或另一种方式)的任何事务都将引发唯一违例的异常,并回滚整个事务。 Uniqueness is guaranteed at all times. 始终保证唯一性。

If you want to log entries that are rejected as duplicates you could encapsulate the INSERT in a server-side function, trap the unique violation and write to a log table instead: 如果要记录被拒绝重复的条目,可以将INSERT封装在服务器端函数中,捕获唯一的违规并写入日志表:

You'll find examples on SO with this search . 通过此搜索,您将找到有关SO的示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM