Почему оптимистическая блокировка activerecord работает только один раз на строку?

Почему-то я всегда получаю их по пятницам.

Мой предыдущий вопрос касался той же проблемы, но теперь я могу немного сузить круг вопросов:

Я играл с этим весь день, пытаясь понять это. У меня есть таблица со столбцом lock_version, указанная таким образом:

add_column :jobs, :lock_version, :integer, :default=>0

И я делаю что-то вроде этого:

foo = job.create!
first = Job.find(foo.id)
second = Job.find(foo.id)

Затем я проверяю, что первая и вторая ссылаются на один и тот же объект - их идентификаторы одинаковы, и я вижу эту строку в базе данных, используя инструмент командной строки mysql.

first.some_attribute_field = 'first'
second.some_attribute_field = 'second'
first.save
second.save

пока проблем нет. Я правильно получаю исключение ActiveRecord::StaleObjectError. ОДНАКО:

first = Job.find(foo.id)
second = Job.find(foo.id)
first.some_attribute_field = 'first'
second.some_attribute_field = 'second'
first.save
second.save

... и ничего не происходит. Оказывается, что единственное время, когда я получаю правильное поведение (выброшенное исключение), это когда первый и второй имеют значение lock_version, равное 0. После первого сохранения, однако, это снова НИКОГДА 0. Что на земле с этим?

Я использую ruby ​​1.8.6 и активную запись 2.2.2

Спасибо...

3 ответа

Решение

Когда вы вызываете first.save во второй раз, значение some_attribute_field уже равно "first", activerecord знает об этом, поэтому он не обновляется в БД, чтобы lock_version не увеличивалась. Второе сохранение работает, так как БД никогда не менялся с "первым".

Попробуйте изменить значение во втором тесте на значение, отличное от "first", чтобы оно отличалось от значения в БД.

Я не Ruby, но оптимистичная блокировка мне знакома, поэтому я постараюсь помочь вам отладить ее.

Я предполагаю, что второе сохранение фактически обновляет базу данных. Если оба объекта имеют разную lock_version И lock_version используется в UPDATE, это просто невозможно (UPDATE обновит ноль строк). Итак, у нас есть только две альтернативы:

  • lock_version не используется в операторе UPDATE или используется неправильно
  • оба объекта получили один и тот же lock_version

(насамом деле, существует третий вариант: оба save() находятся в своей собственной транзакции, но я чувствую, что у вас AUTOCOMMIT = true)

Можете ли вы сделать фактические операторы SQL видимыми? Оператор обновления должен читать что-то вроде

... WHERE JOB_ID=123 AND LOCK_VERSION=8

Когда вы будете иметь под рукой реальные запросы, было бы намного легче понять, что происходит.

PS и еще один: в вашем примере в другой теме у вас есть этот объект:

#<Job id: 323, lock: 8, worker_host: "second">

Вызов save() может игнорироваться контейнером, если свойства не изменены по сравнению со временем загрузки. Я не знаю, если ActiveRecord имеет эту оптимизацию или нет. Но если это произойдет, то второй save() будет проигнорирован, и оптимистическая блокировка не сможет сработать.

Как сказал Владимир, ваш код теста / примера немного ошибочен. Первый не сохраняется в базе данных во время второго сохранения!(), Потому что никакие атрибуты не изменились. Смотрите следующий пример:

foo = Account.create!

first = Account.find(foo.id)
first.cash = 100
first.save!


first = Account.find(foo.id)
first.cash = 100

puts "First lock before " + first.lock_version.to_s
first.save!
puts "First lock after " + first.lock_version.to_s

это производит:

% script/runner another_tester.rb                               
First lock before 1
First lock after 1

Использование вашего примера в версии 2.3.2 rails работает как следует с исключением объекта Stale при сохранении секунды (оба раза!)

Другие вопросы по тегам