📖

テーブル設計 Tips 日記

1 min read

最近知った Tips だけど、名前が分からないので日記にしたw

問題

以下のようなテーブルがある

Foo
id
Table A
id
foo_id
status (open or close)

Foo は複数の Table A レコードを持つ。 Table A の status は open か close の2種類。
特定の foo_id に対して open なレコードは常に一つで、 close のレコードは複数できる。

この場合、どのカラムにも uniq 制約を貼れないため、Table A のレコードを find_or_create_by したい場合に同時アクセスがあると open なレコードが重複してしまう。

ロックをかけないとしたら、どう解決するか。

1つの解決策

親テーブルを作成し last_table_a_id を持たせて、foo_id にユニーク制約をつける。

Parent Table
id
foo_id (uniq)
last_table_a_id
Table A
id
status (open or close)

そして、実際の作成処理では以下のようにトランザクションで囲む。

current_parent_table = ParentTable.find_or_create_by!(foo_id: foo_id) # 重複してたらエラー                                        
return "オープン済み" if current_parent_table.last_table_a&.status == "open"                    

ActiveRecord::Base.transaction do
  new_table = TableA.create
  
  # 別の人が先に Table A 作って古い方を参照していたら、 last_table_a_id がミスマッチで更新できない
  raise "Created by Other" unless ParentTable
      .where(id: current_parent_table.id, last_table_a_id: current_parent_table.last_table_a_id)
      .update_all(last_table_a_id: new_table.id) == 1
end 

こんな方法もあるのねと思った案でした。

Appendix

ユニーク制約を貼ってないテーブルで、ActiveRecord::RecordNotUniqueエラーを発生させる

Discussion

ログインするとコメントできます