トランザクションの観点からは、単一のレコードを変更するよりも、操作を追跡する補助的なレコードを作成する方がはるかに優れています。
たとえば、ユーザーが「賭け」を行うことができるシステムを使用している場合、2 人の間の賭けを定義するある種の Bet クラスが存在するのは当然のことです。ユーザーが賭けを作成すると、関連付けられたリストが大きくなります。Rails の用語では、次のようになります。
class User < ActiveRecord::Base
has_many :bets
has_many :bet_pools,
:through => :bets
end
class Bet < ActiveRecord::Base
belongs_to :user
belongs_to :bet_pool
end
class BetPool < ActiveRecord::Base
has_many :bets
belongs_to :winning_bet
belongs_to :winning_user,
:class_name => 'User',
:through :winning_bet,
:source => :user
end
各賭けの金額は Bet レコードに保存され、BetPool は特定の賭けに対して行われた合計の賭けを表しますが、そのようなことについては別の用語を使用している場合があります。
ベットが精算された場合、bet_pool.which_user アソシエーションを割り当てることにより、勝者を指定できます。
ユーザーの「勝った記録」を知りたい場合は、勝ったすべての賭けプールを表にして金額を合計するのと同じくらい簡単です。
User レコードの一部のプロパティを常に調整したくない理由は、2 つの独立したプロセスがその値を微調整する可能性があり、SQL が適切に実装されていない場合に競合状態になる可能性があるためです。たとえば、ユーザーが賭けをして、非常に短い期間で賭けに勝つ場合があります。
ユーザーが $1000 で開始し、2 つの操作が同時に発生した場合、次のことが発生する可能性があります。
# Process A intending to add $500
user.balance = user.balance + 500
user.save
# Process B intending to deduct $100
user.balance = user.balance - 100
user.save
順番に実行すると、バランスが 1000 から 1500 になり、その後 1400 に下がると予想されますが、2 番目のプロセスはロード時に元の値 1000 で開始され、900 に調整されて保存され、最初の結果が上書きされます。 .
ActiveRecord のインクリメント やデクリメントなど、この種の作業に役立つメソッドがありますが、必要に応じて単純に集計するだけで、最良の結果が得られます。