シーケンスのIDを自前でふる
Railsの場合、ActiveRecordを使って、AutoIncrementでidを発行しているのが一般的。
でも、自前でふりたい場合がある!
主な理由は、テーブルやデータベースを跨いで連番のidをふりたい場合などかなと思います。例えばシャーディングとか(Railsでシャーディングをサポートしていないけど)。
将来的に大量のレコードが出来て、構成を変える可能性がある場合にはあらかじめ自前でidをふっておくと良い事があるかもしれません。
テーブル構成
シーケンスidを管理するテーブルと、シーケンスidを使うテーブルを作る。例として、userテーブルにuser_idをふるシステムを考える。
テーブル構造
userテーブルには、user_idとemail、暗号化したパスワードを入れる想定。
user_sequenceにidがどこまで進んだかを記録しておく。
シーケンスの処理は単純なのでトランザクションを考慮しない方がパフォーマンスが出るためMyISAMで。仮にinnodb側がrollbackしても発行されたidが使われないだけなので問題ない。
* user_sequence
CREATE TABLE `user_sequence` (
`id` int(10) unsigned NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
* user_auths
CREATE TABLE `user_auths` (
`user_id` int(10) unsigned NOT NULL,
`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`encrypted_password` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT ''
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
モデルと処理
シーケンス管理側
"models/user_sequence.rb"
class UserSequence < ActiveRecord::Base
self.table_name = :user_sequence
self.primary_key = :id
end
ユーザーテーブル側
"models/user_auth.rb"
class UserAuth < ActiveRecord::Base
before_save :set_user_id
def set_user_id
if self.user_id.blank?
ActiveRecord::Base.uncached do
UserSequence.connection.execute('UPDATE user_sequence SET id = LAST_INSERT_ID(id + 1)')
user_id = UserSequence.find_by_sql('SELECT SQL_NO_CACHE LAST_INSERT_ID() AS id').first
self.user_id = user_id['id']
end
end
end
end
以下の動作によりシーケンステーブルに+1した後に、その値をuser_idとして利用している。
before_saveのhookでset_user_idを呼び出す
シーケンステーブルに今のID+1をINSERT
user_idにINSERT後(+1された後の)のLAST_INSERT_ID()を入れる
呼び出し元はどこでも良いので、テーブル間でも一貫性の取れたシーケンスIDをふる事が出来る。
まとめ
Rails的にはそもそもシャーディングしないっぽいのでそこまで必要無いのかなと思いつつも、将来的に非常に大きくなりそうなアプリの設計をしていたのでこんな感じで作ってみました。