Я запускаю MySQL на уровне изоляции по умолчанию (REPEATABLE-READ).
Мой код python использует sqlalchemy для управления транзакцией, которая может (и нередко) работать одновременно в двух совпадающих процессах.
c.begin();
# this SELECT establishes the transaction snapshot from which all other SELECTs will read data
c.execute("SELECT something FROM sometable;")
try:
c.execute("INSERT INTO othertable (unique_key) VALUES (1)")
except sqlalchemy.exc.IntegrityError as e:
code, msg = e.orig
if code != 1062:
raise
# duplicate key: another transaction commited the above INSERT so I can't rely on LAST_INSERT_ID
rows = c.execute("SELECT * FROM table WHERE unique_key=1")
inserted_id = None
for id, in rows:
inserted_id = id
break
assert inserted_id is not None
else:
inserted_id = c.last_insert_id()
c.commit()
Точный код, очевидно, немного сложнее со многими другими запросами, но суть проблемы в том, что этот код довольно часто встречается в assert в обработчике исключений.
Причина в том, что эта транзакция часто выполняется одновременно в разных процессах: SELECT, который запускается по дубликатному исключению ключа, терпит неудачу, поскольку моментальный снимок DB, установленный первым SELECT, не содержит вновь вставленную строку (она была вставлена другая транзакция после первого SELECT и до INSERT).
Теперь я понимаю, что могу изменить свои SELECT для использования FOR UPDATE, и это пробило бы отверстие в снимке, чтобы прочитать введенное значение.
Однако я не хочу этого делать, потому что это заставит меня посыпать это в нескольких местах, и я считаю это относительно хрупким.
Существуют ли другие, более надежные, более стандартные, менее эффективные способы борьбы с этими сокровенными вставками (это факт жизни, с которым мне приходится иметь дело).
Добавление искусственного обновления кажется достаточным, чтобы заставить mysql обновить моментальный снимок транзакции:
c.begin();
# this SELECT establishes the transaction snapshot from which all other SELECTs will read data
c.execute("SELECT something FROM sometable;")
try:
c.execute("INSERT INTO othertable (unique_key) VALUES (1)")
except sqlalchemy.exc.IntegrityError as e:
code, msg = e.orig
if code != 1062:
raise
# duplicate key: another transaction commited the above INSERT so I can't rely on LAST_INSERT_ID
# ARTIFICAL UPDATE TO FORCE MYSQL TO UPDATE ITS SNAPSHOT
c.execute("UPDATE table SET value = something WHERE unique_key=1")
rows = c.execute("SELECT * FROM table WHERE unique_key=1")
inserted_id = None
for id, in rows:
inserted_id = id
break
assert inserted_id is not None
else:
inserted_id = c.last_insert_id()
c.commit()
Замечания: