4

我的数据库中有帖子和组织。Posts belongs_to 组织和组织 has_many 帖子。

我的帖子表中有一个现有的 post_id 列,现在我在创建新帖子时手动增加该列。如何将自动增量添加到限定为 organization_id 的列?

目前我使用 mysql 作为我的数据库,但我计划切换到 PostgreSQL,所以如果可能的话,该解决方案应该适用于两者:)

非常感谢!

4

4 回答 4

5

@richard-huxton 有正确的答案并且是线程安全的。

使用事务块并在该事务块内使用 SELECT FOR UPDATE。这是我的 Rails 实现。在 ruby​​ 类上使用“事务”来启动事务块。在要锁定的行上使用“锁定”,基本上阻止所有其他并发访问该行,这是确保唯一序列号所需的。

class OrderFactory
  def self.create_with_seq(order_attributes)
    order_attributes.symbolize_keys!
    raise "merchant_id required" unless order_attributes.has_key?(:merchant_id)
    merchant_id = order_attributes[:merchant_id]

    SequentialNumber.transaction do
      seq = SequentialNumber.lock.where(merchant_id: merchant_id, type: 'SequentialNumberOrder').first
      seq.number += 1
      seq.save!
      order_attributes[:sb_order_seq] = seq.number
      Order.create(order_attributes)
    end
  end
end

我们为后台作业运行 sidekiq,因此我通过创建 1000 个后台作业来测试此方法,以使用 8 个工作人员(每个工作人员有 8 个线程)来创建订单。如果没有锁或事务块,重复的序列号会按预期发生。有了锁和事务块,所有的序列号看起来都是唯一的。

于 2014-07-15T18:24:30.473 回答
4

OK - I'll be blunt. I can't see the value in this. If you really want it though, this is what you'll have to do.

Firstly, create a table org_max_post (org_id, post_id). Populate it when you add a new organisation (I'd use a database trigger).

Then, when adding a new post you will need to:

  1. BEGIN a transaction
  2. SELECT FOR UPDATE that organisation's row to lock it
  3. Increment the post_id by one, update the row.
  4. Use that value to create your post.
  5. COMMIT the transaction to complete your updates and release locks.

You want all of this to happen within a single transaction of course, and with a lock on the relevant row in org_max_post. You want to make sure that a new post_id gets allocated to one and only one post and also that if the post fails to commit that you don't waste post_id's.

If you want to get clever and reduce the SQL in your application code you can do one of:

  1. Wrap the hole lot above in a custom insert_post() function.
  2. Insert via a view that lacks the post_id and provides it via a rule/trigger.
  3. Add a trigger that overwrites whatever is provided in the post_id column with a correctly updated value.

Deleting a post obviously doesn't affect your org_max_post table, so won't break your numbering.

Prevent any updates to the posts at the database level with a trigger. Check for any changes in the OLD vs NEW post_id and throw an exception if there is one.

Then delete your existing redundant id column in your posts table and use (org_id,post_id) as your primary key. If you're going to this trouble you might as well use it as your pkey.

Oh - and post_num or post_index is probably better than post_id since it's not an identifier.

I've no idea how much of this will play nicely with rails I'm afraid - the last time I looked at it, the database handling was ridiculously primitive.

于 2013-06-10T18:08:42.570 回答
1

很高兴知道如何实现它。我更喜欢自己使用宝石。

于 2015-11-04T07:33:20.733 回答
0

首先,我必须说这不是一个好习惯,但我只会专注于解决您的问题:您始终可以通过在 PostsController 上执行以下操作来获取组织的帖子数:

def create
  post = Post.new(...)
  ...
  post.post_id = Organization.find(organization_id).posts.count + 1
  post.save
  ...
end

您不应该自己更改数据库。让 ActiveRecord 处理它。

于 2013-06-10T14:54:27.727 回答