10

A と B という 2 つのテーブルがあります。どちらにも主キー ID があります。それらは多対多の関係、SEC を持っています。

SEC = Table('sec', Base.metadata,
    Column('a_id', Integer, ForeignKey('A.id'), primary_key=True, nullable=False),
    Column('b_id', Integer, ForeignKey('B.id'), primary_key=True, nullable=False)
)

class A():
   ...
   id = Column(Integer, primary_key=True) 
   ...
   rels = relationship(B, secondary=SEC)

class B():
   ...
   id = Column(Integer, primary_key=True) 
   ...

このコードについて考えてみましょう。

a = A()
b1 = B()
b2 = B()
a.rels = [b1, b2]
...
#some place later
b3 = B()
a.rels = [b1, b3]  # errors sometimes

時々、最後の行でエラーが発生します

duplicate key value violates unique constraint a_b_pkey

私の理解では、(a.id、b.id)を「sec」テーブルに再度追加しようとすると、一意の制約エラーが発生すると思います。そういうことですか?もしそうなら、どうすればこれを回避できますか?そうでない場合、なぜこのエラーが発生するのですか?

4

2 に答える 2

3

あなたが言及するエラーは確かにsecテーブルに矛盾する値を挿入することによるものです。以前の変更ではなく、自分が考えている操作によるものであることを確認するには、SQLロギングをオンにして、エラーになる前に挿入しようとしている値を確認します。

多対多のコレクション値を上書きする場合、SQLAlchemyはコレクションの新しいコンテンツをデータベースの状態と比較し、それに応じてdeleteステートメントとinsertステートメントを発行します。SQLAlchemyの内部を調べているのでない限り、このエラーが発生する2つの方法があります。

1つは同時変更です。プロセス1は値a.relsをフェッチし、それが空であることを通知します。一方、プロセス2もa.relsをフェッチし、[b1、b2]に設定して、(a、b1)、(a、 b2)タプル、プロセス1はa.relsを[b1、b3]に設定し、前の内容が空であり、2番目のタプル(a、b1)をフラッシュしようとすると、重複キーエラーが発生します。このような場合の正しいアクションは、通常、トランザクションを上から再試行することです。代わりに、シリアル化可能なトランザクション分離を使用して、重複キーエラーを引き起こすビジネスロジックエラーとは異なるシリアル化エラーを取得できます。

2番目のケースは、rels属性のロード戦略をに設定することでデータベースの状態を知る必要がないことをSQLAlchemyに納得させることができた場合に発生しますnoload。これは、パラメータを追加して関係を定義するlazy='noload'とき、またはクエリを実行するときにクエリを呼び出すときに実行でき.options(noload(A.rels))ます。SQLAlchemyは、secテーブルに、このストラテジーが有効にロードされたオブジェクトに一致する行がないと想定します。

于 2012-10-08T21:46:23.707 に答える