插入重复键错误后SELECT失败

时间:2017-12-14 10:19:17

标签: python mysql transactions sqlalchemy

我正在以默认的隔离级别(REPEATABLE-READ)运行MySQL。

我的python代码使用sqlalchemy来管理一个事务,该事务可以(并且经常)在两个concurent进程中同时运行。

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()

对于更多查询,确切的代码显然有点复杂,但问题的关键在于此代码经常在异常处理程序中遇到断言。

原因是此事务通常在不同进程中同时运行:由于第一个SELECT建立的数据库快照不包含新插入的行,因此复制键异常触发的SELECT失败(它在第一个SELECT之后和INSERT之前由另一个事务插入。

现在,我知道我可以更改我的SELECT以使用FOR UPDATE,这会在快照中打一个洞来读取插入的实际值。

然而,我不愿意这样做,因为这会迫使我把它洒在很多地方,我觉得这是相对脆弱的。

是否还有其他更强大,更标准,更简洁的方法来处理这些精巧的插入物(它们是我必须处理的生活中的事实)。

1 个答案:

答案 0 :(得分: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以某种方式更改行值