Pony ORM сообщает, что запись была обновлена ​​вне текущей транзакции, в то время как другой транзакции нет.

Код довольно прост, а именно:

from pony.orm import Required, Set, Optional, PrimaryKey
from pony.orm import Database, db_session
import time


db = Database('mysql', host="localhost", port=3306, user="root",
                      passwd="123456", db="learn_pony")


class TryUpdate(db.Entity):
    _table_ = "try_update_record"
    t = Required(int, default=0)

db.generate_mapping(create_tables=True)


@db_session
def insert_record():
    new_t = TryUpdate()


@db_session
def update():
    t = TryUpdate.get(id=1)
    print t.t
    t.t = 0
    print t.t


if __name__ == "__main__":
    insert_record()
    update()

pony.orm сообщает об исключении: pony.orm.core.CommitException: объект TryUpdate[1] был обновлен вне текущей транзакции. Но других транзакций нет.

И, как показывают мои эксперименты, pony работает нормально, пока значение t.t изменяется на значение, отличное от исходного, но всегда сообщает об исключении, когда для t.t установлено значение, равное исходному.

Я не уверен, что это дизайнерское решение. Должен ли я проверять, меняется ли мое входное значение каждый раз перед назначением? Или я могу что-нибудь сделать, чтобы избежать этого раздражающего исключения?

моя версия пони: 0.4.8

Большое спасибо~~~


person KurtRao    schedule 15.11.2013    source источник


Ответы (1)


Автор Pony ORM здесь.

Это поведение является специфичной для MySQL ошибкой, которая была исправлена ​​в версии Pony ORM 0.4.9, поэтому обновите ее. Остальная часть моего ответа - это объяснение того, что вызвало ошибку.

Причина этой ошибки следующая. Чтобы предотвратить потерю обновлений, Pony ORM использует оптимистичные проверки. Pony отслеживает, какие атрибуты были прочитаны или изменены во время выполнения программы, а затем добавляет дополнительные условия в раздел WHERE соответствующего запроса UPDATE. Таким образом, Pony гарантирует, что никакие данные не будут потеряны из-за параллельного обновления. Рассмотрим следующий пример:

@db_session
def some_function()
   obj = MyObject[123]
   print obj.x
   obj.x = 100

После выхода из some_function декоратор @db_session зафиксирует текущую транзакцию. Непосредственно перед фиксацией данные объекта будут сохранены следующей командой UPDATE:

UPDATE MyTable
SET x = <new_value>
WHERE id = 123 and x = <old_value>

Вы можете задаться вопросом, зачем было добавлено это дополнительное условие and x = <old_value>? Это связано с тем, что Pony знает, что программа видела предыдущее значение атрибута x и может использовать это значение для вычисления нового значения того же атрибута. Поэтому Pony предпринимает шаги, чтобы гарантировать, что этот атрибут не изменится в момент UPDATE. Этот подход называется "оптимистическая проверка параллелизма" (см. также статью Википедии "оптимистичный контроль параллелизма"). Поскольку уровень изоляции, используемый по умолчанию в большинстве баз данных, не SERIALIZABLE, без этой дополнительной проверки возможно, что какая-то другая транзакция успела обновить значение атрибута x до фиксации нашей транзакции, и тогда значение, записанное параллельной транзакцией, будет потеряно. .

Когда драйвер базы данных Python выполняет запрос UPDATE, он возвращает количество строк, удовлетворяющих критериям UPDATE. Таким образом, Pony узнает, было ли обновление успешным или нет. Если результат равен 1, это означает, что одна строка была успешно найдена и обновлена, но если результат равен 0, это означает, что строка уже была изменена другой транзакцией и теперь она не удовлетворяет критериям раздела WHERE. Когда это происходит, Pony завершает текущую транзакцию, чтобы предотвратить потерю обновления.

Причина ошибки в том, что в то время как все другие драйверы баз данных возвращают количество строк, которые были найдены по критерию секции WHERE, драйвер MySQLdb по умолчанию возвращает количество строк, которые были фактически изменены! Из-за этого, если новое значение атрибута оказывается таким же, как исходное значение того же атрибута, MySQLdb сообщает, что было изменено 0 строк, а Pony (до релиза 0.4.9) ошибочно полагает, что это означает что строка была изменена параллельной транзакцией. Начиная с версии 0.4.9 Pony ORM указывает драйверу MySQLdb вести себя стандартным образом и возвращать количество строк, которые были найдены, а не количество строк, которые были фактически обновлены.

Надеюсь это поможет :)

P.S. Я случайно нашел ваш вопрос, чтобы надежно получить ответы о Pony ORM, я рекомендую вам присылать вопросы в наш список рассылки http://ponyorm-list.ponyorm.com. Если вы считаете, что нашли ошибку, вы можете открыть проблему здесь: https://github.com/ponyorm/pony/issues. Спасибо Вам за Ваш вопрос!

person Alexander Kozlovsky    schedule 16.11.2013
comment
Это очень помогает! Большое спасибо~ - person KurtRao; 20.11.2013
comment
Это здорово ^_^! Можете ли вы тогда выбрать ответ как принятый? ;) - person Alexander Kozlovsky; 20.11.2013
comment
У меня все еще есть эта проблема с версией 0.7.3 (mysql), если вам нужна дополнительная информация, я опубликую проблему на github. - person gone; 04.06.2018