我目前正在玩交易,无法解决以下问题:
鉴于用户名为“johnny”,全名为“John Smith”。
我启动两个rails控制台并按此顺序执行以下命令:
控制台A:
ActiveRecord::Base.transaction { user = User.find_by_username("foo"); sleep 10; user.update_attribute(:full_name, "#{user.full_name}-1"); }
控制台B:
ActiveRecord::Base.transaction { user = User.find_by_username("foo"); sleep 10; user.update_attribute(:full_name, "#{user.full_name}-2"); }
所以时间安排如下:
读“约翰史密斯”
B读“John Smith”
A写道“John Smith-1”
B写道“John Smith-2”
根据我的数据库类事务,B应该无法写入“John Smith-2”,因为数据自读取后就已更改。因此,交易应该回滚,交易A应该赢。我希望用户名是“John Smith-1”,但结果是“John Smith-2”。
为什么会发生这种情况或如何获得预期行为?
亲切的问候
尼尔斯
答案 0 :(得分:4)
据我所知,事务不是关于锁定,事务的主要目的是确保原子的变化。例如,当您从支票账户中扣除资金并将其存入储蓄账户时,您需要确保INSERT成功或两者都失败,否则您将处于不一致状态。你需要的是锁定,例如http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
更新:根据我的理解,ACID并不意味着回滚交易。如果没有错误,则两个事务都将成功。你得到的结果取决于隔离。如果使用SERIALIZABLE
级别,您将获得“John Smith-1-2”,但InnoDB默认使用REPEATABLE READ
级别http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read,这意味着如果SELECT
是User.find_by...
非锁定(SELECT
)它不会锁定记录以进行读取,并且您从事务A开始时创建的快照获得原始结果(即来自B的SERIALIZABLE
将不会锁定,直到A完成为如果是{{1}})。
更新:同时您可以检查http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html是否存在悲观锁定。