Ошибка SELECT после ошибки вставки повторяющегося ключа

0

Я запускаю 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, и это пробило бы отверстие в снимке, чтобы прочитать введенное значение.

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

Существуют ли другие, более надежные, более стандартные, менее эффективные способы борьбы с этими сокровенными вставками (это факт жизни, с которым мне приходится иметь дело).

Теги:
transactions
sqlalchemy

1 ответ

0

Добавление искусственного обновления кажется достаточным, чтобы заставить 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()

Замечания:

  1. MariaDB 10.2.9, похоже, удовлетворена любым утверждением UPDATE в этой строке (включая обновления значений, которые не изменяют значения строк), чтобы вызвать обновление моментального снимка
  2. MySQL 5.6.28 игнорирует обновление моментального снимка, если UPDATE не изменит значения строк каким-либо образом

Ещё вопросы

Сообщество Overcoder
Наверх
Меню