2017-12-14 3 views
0

MySQLをデフォルトの分離レベル(REPEATABLE-READ)で実行しています。重複キーエラーの挿入後にSELECTが失敗する

私のPythonコードは、2つの同時プロセス内で同時に実行できるトランザクションを管理するために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で確立されたDBスナップショットに新しく挿入された行が含まれていないため、重複キー例外でトリガーされるSELECTが失敗します最初のSELECTの後でINSERTの前に別のトランザクションによって挿入されました)。

私は自分のSELECTをFOR UPDATEを使用するように変更でき、挿入された実際の値を読み取るためにスナップショットに穴をあけることを理解しました。

しかし、私はこれをすることを躊躇しています。なぜなら、これはかなりの場所でこれを振りかけることになり、これは比較的壊れやすいと私は見ています。

これらのコンクレインインサートを処理するには、他のより堅牢で標準的で控えめではない方法があります(これは私が処理しなければならない事実です)。

答えて

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が変更されない限りスナップショット更新を無視します何とか行の値が
関連する問題