web-dev-qa-db-ja.com

データベーストランザクションは競合状態を防ぎますか?

データベースシステムのトランザクションが何をするのか、私には完全にはわかりません。更新のリストを完全にロールバックするために使用できることは知っていますが(たとえば、あるアカウントでお金を差し引いて別のアカウントに追加するなど)、それだけで十分ですか?具体的には、競合状態を防ぐために使用できますか?例えば:

// Java/JPA example
em.getTransaction().begin();
User u = em.find(User.class, 123);
u.credits += 10;
em.persist(u); // Note added in 2016: this line is actually not needed
em.getTransaction().commit();

(これはおそらく単一の更新クエリとして記述できることはわかっていますが、常にそうであるとは限りません)

このコードは競合状態から保護されていますか?

私は主にMySQL5 + InnoDBに興味がありますが、一般的な回答も歓迎します。

32

TL/DR:トランザクションは not 本質的にすべての競合状態を防ぎません。実際のすべてのデータベース実装では、ロック、中止と再試行の処理、またはその他の保護手段が必要です。 トランザクションは、すべての同時実行効果から安全にするためにクエリに追加できる秘密のソースではありません

隔離

あなたがあなたの質問で得ているのは[〜#〜] i [〜#〜] in [〜#〜] acid [〜#〜] -- 分離 。学術的に純粋な考えは、トランザクションは完全な分離を提供する必要があるため、結果はすべてのトランザクションが連続して実行された場合と同じになるというものです。実際には、実際のRDBMS実装ではめったにありません。機能は実装によって異なり、READ COMMITTEDのような弱い分離レベルを使用することでルールを弱めることができます。実際には、SERIALIZABLEの分離であっても、トランザクションがすべての競合状態を妨げるとは限りません。

一部のRDBMSは、他のRDBMSよりも強力な機能を備えています。たとえば、PostgreSQL 9.2以降では、トランザクション間のほとんど(すべてではない)の可能な相互作用を検出し、を除くすべてを中止する、非常に優れたSERIALIZABLE分離があります。競合するトランザクションの1つ。そのため、トランザクションを非常に安全に並行して実行できます。

あるとしてもごくわずか3、システムには真に完璧なSERIALIZABLE分離があり、ロックのエスカレーションやロックの順序付けのデッドロックなどの問題を含む、起こりうるすべての人種や異常を防ぎます。

強力な分離を使用しても、一部のシステム(PostgreSQLなど)は、トランザクションを待機させてシリアルに実行するのではなく、競合するトランザクションを中止します。アプリは、実行内容を記憶し、トランザクションを再試行する必要があります。そのため、トランザクションによって同時実行関連の異常がDBに格納されるのを防ぎましたが、アプリケーションに対して透過的ではない方法で格納されています。

アトミシティ

おそらく、データベーストランザクションの主な目的は、アトミックコミットを提供することです。トランザクションをコミットするまで、変更は有効になりません。コミットすると、他のトランザクションに関する限り、変更はすべて同時に有効になります。トランザクションは、トランザクションが行った変更の一部だけを見ることができません。1,2。同様に、ROLLBACKの場合、トランザクションの変更は他のトランザクションには表示されません。まるであなたの取引が存在しなかったかのようです。

それは[〜#〜] a [〜#〜] in [〜#〜] acid [〜#〜]です。

耐久性

もう1つは耐久性です[〜#〜] d [〜#〜] in [〜#〜] acid [〜#〜]。トランザクションをコミットするときに、停電や突然の再起動などの障害に耐えられるように、トランザクションをストレージに本当に保存する必要があることを指定します。

一貫性:

ウィキペディア を参照してください

楽観的同時実行制御

ロックや高い分離レベルを使用するのではなく、Hibernate、EclipseLinkなどのORMが 楽観的同時実行制御 (しばしば「楽観的ロック」と呼ばれる)を使用するのが一般的です。 ")パフォーマンスを維持しながら、より弱い分離レベルの制限を克服します。

このアプローチの重要な機能は、複数のトランザクションにまたがって作業を行うことができることです。これは、ユーザー数が多く、特定のユーザーとの対話の間に長い遅延が発生する可能性があるシステムにとって大きなプラスです。

参考文献

テキスト内のリンクに加えて、 ロック、分離、および並行性に関するPostgreSQLドキュメントの章 を参照してください。別のRDBMSを使用している場合でも、それが説明する概念から多くのことを学ぶことができます。


1簡単にするために、ここではめったに実装されないREAD UNCOMMITTED分離レベルを無視しています。ダーティリードを許可します。

2@meritonが指摘しているように、当然の結果は必ずしも真実ではありません。 ファントム読み取りSERIALIZABLEより下のすべてで発生します。進行中のトランザクションの一部は(まだコミットされていないトランザクションによって)一部の変更を確認しません。次に、進行中のトランザクションの次の部分は変更を確認します他のトランザクションがコミットしたとき。

3 ええと、IIRC SQLite2は、書き込みが試行されたときにデータベース全体をロックすることによって機能しますが、それは並行性の問題に対する理想的な解決策とは言えません。

28
Craig Ringer

データベース層は、分離レベルと呼ばれるさまざまな程度のトランザクションのアトミック性をサポートします。サポートされている分離レベルとそのトレードオフについては、データベース管理システムのドキュメントを確認してください。最強の分離レベルSerializableでは、トランザクションを1つずつ実行するかのように実行する必要があります。これは通常、データベースで排他ロックを使用して実装されます。これはデッドロックを引き起こす可能性があり、データベース管理システムは、関連するトランザクションをロールバックすることでデッドロックを検出して修正します。このアプローチは、しばしば悲観的ロックと呼ばれます。

多くのオブジェクトリレーショナルマッパー(JPAプロバイダーを含む)も楽観的ロックをサポートします。この場合、更新の競合はデータベースでは防止されませんが、アプリケーション層で検出され、トランザクションがロールバックされます。楽観的ロックを有効にしている場合、サンプルコードを通常実行すると、次のSQLクエリが生成されます。

select id, version, credits from user where id = 123;  

これが(123、13、100)を返すとしましょう。

update user set version = 14, credit = 110 where id = 123 and version = 13;

データベースは、更新された行数を示します。 1つの場合、競合する更新はありませんでした。ゼロの場合、競合する更新が発生し、JPAプロバイダーは

rollback;

例外をスローして、アプリケーションコードが失敗したトランザクションを処理できるようにします(再試行など)。

要約:どちらのアプローチでも、競合状態からステートメントを安全にすることができます。

15
meriton

分離レベルに依存します(シリアル化可能では、一般にシリアル化可能でトランザクションは並列ではなく順番に処理されるため、競合状態を防ぎます(または、少なくとも排他的ロックが使用されるため、同じ行を変更するトランザクションは、競合状態を防ぐために、レコードを手動でロックすることをお勧めします(たとえば、mysqlは、選択されたレコードの書き込みロックを取得する「select ... forupdate」ステートメントをサポートします)。

4

特定のrdbmsによって異なります。通常、トランザクションは、クエリ評価計画中に決定されたとおりにロックを取得します。テーブルレベルのロック、他の列レベル、他のレコードレベルを要求できるものもあり、パフォーマンスのために2番目が優先されます。あなたの質問に対する簡単な答えはイエスです。

言い換えると、トランザクションは、一連のクエリをグループ化し、それらをアトミック操作として表すことを目的としています。操作が失敗した場合、変更はロールバックされます。使用しているアダプターが何をするのか正確にはわかりませんが、トランザクションの定義に準拠していれば問題ありません。

これは競合状態の防止を保証しますが、飢餓やデッドロックを明示的に防止するものではありません。トランザクションロックマネージャーがそれを担当します。テーブルロックが使用されることもありますが、同時操作の数を減らすという高額な代償が伴います。

1
Candide