Почему оптимистичная блокировка activerecord работает только один раз для каждой строки?
-
03-07-2019 - |
Вопрос
Почему-то я всегда получаю их по пятницам.
Мой предыдущий вопрос касался той же проблемы, но теперь я могу немного сузить круг вопросов:
Я играл с этим весь день, пытаясь разобраться в этом.У меня есть таблица с столбцом 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 (оба раза!)