Почему оптимистическая блокировка 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 при сохранении секунды (оба раза!)