概要
railsのシステムでは複合主キーは認められてません。全てテーブルがidという自動採番の主キーを持つのが決まりです。
それはわかっていたのですが、多対多の中間テーブル、例えば、モデルで言えば下記ようなものです。
class User < ApplicationRecord
has_many :user_skills
has_many :skills, inverse_of: :users, through: :user_skills
end
class Skill < ApplicationRecord
has_many :user_skills
has_many :users, inverse_of: :skills, through: :user_skills
end
# これが私のいうところの中間テーブルです。
class UserSkill < ApplicationRecord
belongs_to :skill
belongs_to :user
end
これだけは自分の経験上納得がいかないというか、先入観も大きかったと思いますが、下記の理由で複合主キーを使っていました。
- 必要のないIDの分の無駄な容量が増える
- 頻繁にすげ替えが行われるとIDが枯渇するんじゃないか?
ただ、複合主キーで運用してみていくつかの問題がわかっています。
- UserSkillをレシーバーにして削除ができない。
- UserSkillをレシーバーにして更新ができない。
- UserSkillをeager_loadできない。
他にもあるかもしれませんが、今の所私が把握してるのは上記です。
最近は容量に関して言えばストレージもだいぶ低価格になってきてるし、アクセス速度の早いストレージも低価格になってきてます。IDの枯渇に関してはbigintであれば一般的なシステムでは多分問題にならないので、気にしなくてもいいかもと思うようになってきました。
こういうGemもあるのですが、いつまでメンテナンスされるかわからないし、深いところに手を入れてるのでメンテナンスが止まったとき自分でどうにかするコストがかかりそうだと思いました。
そこでRailsの恩恵を受けた方がいいと思うテーブルに関しては複合主キーをやめてみようと思い、そのmigarationを書いてみました。
class UserSkillToSinglePKey < ActiveRecord::Migration[5.2]
def change
remove_foreign_key :user_skill, :skill
remove_foreign_key :user_skill, :user
reversible do |change|
change.up do
execute 'ALTER TABLE user_skill DROP PRIMARY KEY'
end
change.down do
execute 'ALTER TABLE user_skill ADD PRIMARY KEY (skill_id, user_id)'
end
end
add_column :user_skill, :id, :primary_key, unsigned: true, first: true
add_foreign_key :user_skill, :skill
add_foreign_key :user_skill, :user
end
end
ポイントとしては生SQLのところでreversible
を使ってる点、foreign_keyを外さないと主キーをDROPできないことと、add_column :user_skill, :id, :primary_key
とすると、勝手にAUTOINCREMENTになるところくらいですかね。
もし、運用しているサービスにやるのであれば、テーブルロックがかかるのでレコード数によっては問題になるかもしれません。また、一度外部制約を外すので不整合が起きる可能性があるとは思いますのでその点のも考慮に入れてください。