ふとした時に lock! の挙動が気になるのでメモしておく。
前提
- Postgresql
テスト1
以下のコードを console A, console B 両方に貼り付けて、A -> B の順に実行
ActiveRecord::Base.transaction do
User.first.lock!
sleep 10
puts 'foo'
end
console A
TRANSACTION (0.1ms) BEGIN
User Load (0.8ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 FOR UPDATE [["id", 4], ["LIMIT", 1]]
foo
TRANSACTION (2.4ms) COMMIT
=> nil
console B
select for update が走るが、console A の終了を待ってから sleep 10 が走る
テスト2
# console A
ActiveRecord::Base.transaction do
User.first.lock!
sleep 10
puts 'foo'
end
# console B
User.first.update_confirmation_token!
console A
テスト1と同じ
console B
user の select は走るが、A を待ってから実行される
テスト3
# console A
ActiveRecord::Base.transaction do
User.first.lock!
sleep 10
puts 'foo'
end
# console B
User.first.clients.create!(
name: 'foo',
)
console A
テスト1と同じ
console B
user の select は走るが、A を待ってから INSERT が実行される
補足
console B は以下にしても同じ
Client.create!(
user: User.first,
name: 'foo',
)
テスト4
# console A
ActiveRecord::Base.transaction do
User.first.lock!
sleep 10
puts 'foo'
end
# console B
User.first.clients.last.update(
name: 'bar',
)
console A
テスト1と同じ
console B
update まで即実行される
テスト5
# console A
ActiveRecord::Base.transaction do
User.first.lock!
sleep 10
puts 'foo'
end
User.first.clients.last.reports.create!(
date: Date.current,
)
console A
テスト1と同じ
console B
insert が即実行される
参考
行レベルロックは、データの問い合わせには影響を与えません。 行レベルロックは、同じ行に対する書き込みとロックだけをブロックします。