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

StackOverflow https://stackoverflow.com/questions/813535

Вопрос

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

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

Я играл с этим весь день, пытаясь разобраться в этом.У меня есть таблица с столбцом 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

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

Я использую ruby 1.8.6 и active record 2.2.2

Спасибо...

Это было полезно?

Решение

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

Попробуйте изменить значение во втором тесте на что-то отличное от "first", чтобы оно отличалось от того, что есть в базе данных.

Другие советы

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

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

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

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

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

... WHERE JOB_ID=123 AND LOCK_VERSION=8

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top