【问题标题】:SELECT fails after insert duplicate key error插入重复键错误后 SELECT 失败
【发布时间】:2017-12-14 10:19:16
【问题描述】:

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

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

确切的代码显然要复杂一些,查询更多,但问题的症结在于该代码经常在异常处理程序中命中断言。

原因是这个事务经常在不同的进程中同时运行:在重复键异常上触发的SELECT失败,因为第一个SELECT建立的DB快照不包含新插入的行(它由另一个事务在第一个 SELECT 之后和 INSERT 之前插入)。

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

但是,我不愿意这样做,因为它会迫使我将它洒在很多地方,而且我认为它相对脆弱。

还有其他更强大、更标准、更简洁的方法来处理这些并发插入(它们是我必须处理的生活事实)。

【问题讨论】:

    标签: python mysql transactions sqlalchemy


    【解决方案1】:

    添加人工更新似乎足以强制 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 似乎对此行的 any UPDATE 语句(包括不更改行值的值更新)触发快照更新感到满意
    2. MySQL 5.6.28 忽略快照更新,除非 UPDATE 以某种方式更改行值

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-12
      • 1970-01-01
      • 2011-10-04
      • 1970-01-01
      • 2011-01-26
      • 2013-07-05
      相关资源
      最近更新 更多