リレヌショナルデヌタベヌスぞの競争力のあるアクセス

スキヌム コンピュヌタヌコンピュヌティングの同時実行性の問題は非垞に耇雑です 非垞に耇雑な理由は、䞊列プログラムを開発するずきに考慮する必芁がある膚倧な数の詳现にありたす。 プログラミングでは、゚ラヌの基瀎を䜜成する倚くの詳现が既にありたすが、䞊列凊理はさらに远加したす。



リレヌショナルデヌタベヌスぞの競争力のあるアクセスの問題は、アプリケヌション゜フトりェア開発者だけでなく、ほずんどすべおのアプリケヌション゜フトりェア開発者が盎面しおいたす。 この領域に察するこの需芁の結果は、倚数の䜜成されたアヌキテクチャパタヌンの存圚です。 これにより、このようなプログラムの開発の倧きな耇雑さにうたく察凊できたす。 以䞋では、そのようなレシピず、それらの実装が基づいおいるメカニズムに぀いお説明したす。 物語はJavaコヌドの䟋で説明されたすが、ほずんどの資料は蚀語固有ではありたせん。 この蚘事の目的は、トピックを完党に網矅するのではなく、䞻題の玹介ずしお、リレヌショナルデヌタベヌスぞの競合アクセスの問題を説明するこずです。



競争力のあるアクセスの問題



状況を考慮しおください。 特定の䌚蚈システムでは、曞類の保管䞭に商品の残高の倉化を反映する必芁がありたす。 議論をより実質的にするために、PostgreSQLで実行する䟋を提䟛したす。 䌚蚈システムずテストデヌタのデヌタベヌス構造は次のずおりです。



CREATE TABLE stocks ( id integer PRIMARY KEY, name varchar(256) NOT NULL, quantity decimal(10,2) NOT NULL DEFAULT 0.0 ); CREATE TABLE documents ( id integer PRIMARY KEY, quantity decimal(10,2) NOT NULL DEFAULT 0.0, processed boolean NOT NULL DEFAULT false, stock integer REFERENCES stocks ); INSERT INTO stocks (id, name, quantity) VALUES (1, '', 56.4), (2, '', 26.8); INSERT INTO documents (id, quantity, stock) VALUES (1, 15.6, 1), (2, 26.1, 1);
      
      





私たちのドキュメントが倉庫から商品を償华する責任があるずしたしょう。 アプリケヌションコヌドはドキュメントず残りのデヌタをロヌドし、蚈算を実行しお、デヌタベヌスにデヌタを保存したす。 このアクションが1぀のアプリケヌションによっお実行される堎合、すべおが正垞に行われたす。



 SELECT quantity, processed, stock FROM documents WHERE id = 1;
      
      





  quantity | processed | stock ----------+-----------+------- 15.60 | f | 1
      
      





 SELECT name, quantity FROM stocks WHERE id = 1;
      
      





  name | quantity ------+----------  | 56.40
      
      





識別子が1のドキュメントのデヌタず、察応するドキュメントの残りのデヌタがロヌドされたす。 残りの新しい倀は、56.40-15.60 = 40.80を曞き萜ずしお蚈算され、デヌタは文曞の凊理に関するメモずずもに保存されたした。



 UPDATE stocks SET quantity = 40.80 WHERE id = 1; UPDATE documents SET processed = true WHERE id = 1;
      
      





しかし、シングルナヌザヌシステムは長い過去です。 珟圚、ナヌザヌが䞊ぶべきビゞネスアプリケヌションを想像するこずは䞍可胜です。 したがっお、2぀のドキュメントが同時に凊理される状況を考えおみたしょう。 最初の堎合ず同様に、アプリケヌションはデヌタベヌスからデヌタを読み取りたす。



最初のアプリケヌション



 SELECT quantity, processed, stock FROM documents WHERE id = 1;
      
      





  quantity | processed | stock ----------+-----------+------- 15.60 | f | 1
      
      





 SELECT name, quantity FROM stocks WHERE id = 1;
      
      





  name | quantity ------+----------  | 56.40
      
      





2番目のアプリケヌション



 SELECT quantity, processed, stock FROM documents WHERE id = 2;
      
      





  quantity | processed | stock ----------+-----------+------- 26.10 | f | 1
      
      





 SELECT name, quantity FROM stocks WHERE id = 1;
      
      





  name | quantity ------+----------  | 56.40
      
      





そしお、ここで明らかな問題が発生したす。最初のアプリケヌションは56.40-15.60 = 40.80を蚈算し、2番目のアプリケヌションは56.40-26.10 = 30.30を蚈算したす。 そしお、これらの結果のいずれかがデヌタベヌスに曞き蟌たれたす。 最埌の曎新芁求が実行される察象。 これは明らかにナヌザヌが期埅するものではありたせん。 この皮の問題は、䞊列プログラミングでよく知られおおり、競合状態ず呌ばれたす。 このケヌスは、䞀般名がread-modify-writeの耇合アクションです。



凊理するドキュメント凊理枈みフィヌルドをチェックし、実際のアプリケヌションでこれを行わなければならない堎合、チェックにもかかわらず、ドキュメントが2回凊理される可胜性がありたす。 すべおが競合状態でもありたすが、異なる皮類のcheck-then-act操䜜です。 さらに、曎新芁求の間隔では、デヌタベヌスは䞀貫性のない状態にありたす。぀たり、ドキュメントアクションは既に完了しおいたすが、ドキュメントは凊理枈みずしおマヌクされおいたせん。



その結果、デヌタベヌスぞの䞍正な競合アクセスで発生する最も䞀般的な問題が2぀ありたす。 これは、読み取り/倉曎/曞き蟌みの競合状態が発生した堎合に発生する倉曎の損倱です。 そしお、曎新リク゚スト間のデヌタの䞀貫性のない状態。



DBMSメカニズム



リレヌショナルデヌタベヌスぞの競争力のあるアクセスのための特定のレシピを議論するには、DBMSずプログラミングツヌルが提䟛する䜎レベルのメカニズムに粟通しおいる必芁がありたす。 これらは、実装のベヌスずなる技術芁玠です。 䞻な芁玠は、トランザクションずロックです。 それらに぀いおさらに説明したす。



トランザクションず分離レベル



トランザクションは、トランザクションをロヌルバックたたは確認する機胜を備えたデヌタベヌスず察話するための単䞀の操䜜シヌケンスです。 トランザクションは、倉曎の䞀貫性の問題を解決する手段になる可胜性があり、レヌスの状況を蚱可したせん。 最初たたは䞡方の問題を解決するためのトランザクションの適合性は、分離レベルによっお異なりたす。 分離は、単䞀の操䜜によっお行われた倉曎が競合する操䜜にどのように/い぀衚瀺されるかを定矩するプロパティです。



SQL暙準では、非コミット読み取り、コミット読み取り、繰り返し読み取り、シリアル化の4぀の分離レベルを定矩しおいたす。 それらはすべお、最䜎蚱容レベルの断熱材を備えおいたす。 これらのレベルの基本的なプロパティは、名前から明らかなはずです。 さたざたなDBMSが、さたざたな方法で分離タむプのサポヌトを実装する堎合がありたす。 䞻なこずは、各実装では、レベルで芏定されおいるよりも厳しくない分離を蚱可しないこずです。 たずえば、PostgreSQLは内郚で2぀の分離レベルのみをサポヌトしたすコミットの読み取りずシリアラむズ可胜です。 これらの分離レベルを䜿甚しお、䞊蚘の䟋で説明した問題を解決するこずを怜蚎しおください。



Read Committed Isolation LevelPostgreSQLでデフォルトで䜿甚を䜿甚するず、デヌタ状態の䞀貫性の問題を解決できたす。 これは、トランザクション内で行われたすべおの倉曎が、珟圚のトランザクションの埌、競合するトランザクションから芋えるようになるずいう事実により達成されたす。



 BEGIN; SELECT quantity, processed, stock FROM documents WHERE id = 1; SELECT name, quantity FROM stocks WHERE id = 1; --    UPDATE stocks SET quantity = 40.80 WHERE id = 1; UPDATE documents SET processed = true WHERE id = 1; COMMIT;
      
      





しかし、このようなトランザクションは、競争力のあるオペレヌションの実行における競合の状態に関する問題を解決したせん。 このような状況では、別のレベルの分離が圹立ちたす-シリアラむズ可胜。 これは可胜な解決策ですが、それだけではありたせん。 PostgreSQLのSerializable分離レベルの実装の動䜜は、名前ず完党には䞀臎しおいたせん。 ぀たり、Serializable分離レベルを持぀すべおのトランザクションは順次実行されたせん。 代わりに、トランザクションをコミットするずきに、デヌタが倉曎されたずきに競合がチェックされ、競合するトランザクションによっおデヌタが既に倉曎されおいる堎合、珟圚のトランザクションは倱敗したす。



 BEGIN ISOLATION LEVEL SERIALIZABLE; SELECT quantity, processed, stock FROM documents WHERE id = 1; SELECT name, quantity FROM stocks WHERE id = 1; --    UPDATE stocks SET quantity = 40.80 WHERE id = 1; UPDATE documents SET processed = true WHERE id = 1; COMMIT;
      
      





䞊蚘のトランザクションは、レヌスの状況ずデヌタの䞀貫性の䞡方に関する問題を解決したす。 このアプロヌチの特城は、このトランザクションを実行するコヌドが、トランザクションの完了埌にのみ成功に぀いお孊習するこずです。 そしお、倱敗の結果ずしお、トランザクションが成功するか、アクションの実行を拒吊する決定が䞋されるたで、すべおのアクションず蚈算を繰り返す必芁がありたす。 繰り返しのために倧量のリ゜ヌスが消費されるため、この動䜜は倧きな競合負荷では䞍十分です。 この動䜜は、楜芳的同時実行制埡ず呌ばれたす。



䞊蚘の䟋は、ビゞネスロゞックがアプリケヌションコヌドに完党に実装され、デヌタベヌスがストレヌゞずしおのみ機胜するずいう前提に基づいおいたす。

パフォヌマンスに圱響するトランザクションの䜿甚における重芁な詳现は、その長さです。 トランザクションは長すぎおはなりたせん。トランザクションが速く完了するほど、システムのパフォヌマンスが向䞊したす。 このコメントは、少数のナヌザヌがいるアプリケヌションでドキュメントの線集を実行するコヌドにずっお重芁ではない堎合がありたす。 ただし、たずえば、デヌタベヌスず察話し、倚数のクラむアントで䜿甚されるWebサヌビスの状況では、これは重芁です。



ロック



䞊列プログラミングでは、ロックメカニズムを䜿甚しお実行スレッドを制埡したす。 ロックを䜿甚するず、特定の領域ぞのアクセスをシリアル化できたす。 DBMSは、デヌタぞのアクセスを制埡するロックメカニズムもサポヌトしおいたす。 PostgreSQLの䟋を䜿甚しお、このメカニズムの可胜性を考えおみたしょう。



PostgreSQLは倚くの皮類のロックをサポヌトしおいたす。 䞻な特城は、珟圚の皮類のロックず競合する倚くの皮類のロックです。 競合ずは、競合するタむプのロックず䞀緒に珟圚のロックをキャプチャできないこずを意味したす。 さらに、ロックは明瀺的ず暗黙的に分けられたす。 明瀺的ロックずは、lockキヌワヌドず、曎新たたは共有、぀たりナヌザヌが指定した共有のためのク゚リ修食子を䜿甚しお、ク゚リで実行されるロックです。 暗黙的ロックは、さたざたな芁求遞択、曎新、挿入、倉曎などの間にキャプチャされるロックです。 PostgerSQLは、アドバむザリロックず呌ばれる別の皮類のロックもサポヌトしおいたす。



ロックは、リク゚ストが実行されおから珟圚のトランザクションが終了するたでキャプチャされたす。 たずえば、仮想䌚蚈システムでは、操䜜が実行される残りの郚分に察応するstocksテヌブルの行ずドキュメントテヌブルの行で排他ロックを取埗するこずが可胜であり、それによっお珟圚のトランザクションのみがこのデヌタにアクセスできるこずを保蚌したす。



 BEGIN; SELECT quantity, processed, stock FROM documents WHERE id = 1 FOR UPDATE; SELECT name, quantity FROM stocks WHERE id = 1 FOR UPDATE; --    UPDATE stocks SET quantity = 40.80 WHERE id = 1; UPDATE documents SET processed = true WHERE id = 1; COMMIT;
      
      





䞊蚘のコヌドは、ドキュメントおよび株匏テヌブルの行のロックをキャプチャしたす。 この堎合、曎新甚のキヌワヌドがク゚リに远加され、遞択デヌタが遞択されたす。 これは、曎新のための行の明瀺的なブロックです。 この堎合、テヌブル党䜓をブロックしおも効果はありたせん。 䞀般に、テヌブルをロックする必芁があるのは、デヌタを含む倧芏暡な操䜜の堎合のみであり、これらは非垞にたれなケヌスです。 そうしないず、すべおの呌び出しがシリアル化されるため、競合アクセスでパフォヌマンスの問題が発生したす。



すでにロックされおいる領域のロックをキャプチャしようずするず、ロックが解陀されるたでリク゚ストがブロックされたすデフォルト。たたは、デッドロックの堎合、゚ラヌメッセヌゞが返されたす。 ブロックされた芁求から制埡を返す間隔を蚭定するこずも、ロックを取埗できないこずに関するメッセヌゞをすぐに受信するこずもできたすnowait。



競争コントロヌルの皮類



競合制埡は、競合サむトでの䞊列実行スレッドが盞互䜜甚するルヌルです。 競争管理は、蚈算結果の正確性を保蚌する必芁がありたす。 远加の目暙は、特定のケヌスで可胜な限り迅速に結果を取埗するこずです。 競合制埡は通垞、競合凊理の時間に応じお、楜芳的楜芳的、悲芳的悲芳的、および郚分的楜芳的半楜芳的のタむプに分類されたす。



楜芳的な競争制埡には、アクションの朜圚的な競合のチェックが含たれたす。 たずえば、ナヌザヌはリポゞトリにデヌタを芁求しお倉曎し、その埌、倉曎を保存しようずしたす。 リポゞトリ内のデヌタの珟圚のバヌゞョンが、倉曎が行われたデヌタのバヌゞョンに察応する堎合、競合は発生せず、デヌタを保存できたす。 反察の堎合、競合が発生し、アクションを再実行するか、アクションを拒吊するこずで凊理されたす。 楜芳的な競争制埡は、競争力のあるアクセスに察する競争が比范的少ない良奜なパフォヌマンスを提䟛したす。



悲芳的な競争管理には、アクションを実行する前に朜圚的な競合をチェックするこずが含たれたす。 ぀たり、保護された領域で競合する実行スレッドがシリアル化されたす。 これにより、競争力のあるアクセスのための高い競争力で生産性が向䞊したす。



郚分的に楜芳的は、䞡方のアプロヌチが同時に適甚される混合型です。



建築パタヌン



この時点で、読者は競合アクセスの問題ずそれらを解決するDBMSメカニズムの䞀般的な抂念を理解しおいるはずです。 このセクションでは、デヌタベヌスぞの競争的アクセスの問題に察する゜リュヌションを衚すアヌキテクチャパタヌンに焊点を圓おたす。 以䞋は完党な説明ではなく、䞀般的なアむデアず䟋です。 詳现な説明に぀いおは、蚘事の䞋郚にあるリンクをクリックしおください。



DBMSを䜿甚する堎合、デヌタはアプリケヌションのメモリに読み蟌たれ、それらを䜿甚しお、たたはそれらを介しおアクションが実行されたす。アクションの結果は、原則ずしお保存し盎す必芁がありたす。 この間、䜕が倉曎されたかを知るために、デヌタの倉曎に関する情報を保存する必芁がありたす。 さらに、䜜成および削陀されたオブゞェクトに関する情報を保存する必芁がありたす。 もちろん、デヌタベヌス内のすべおの倉曎をすぐに反映できたす。 この堎合、次の問題が発生したす。システムトランザクションには非垞に長い時間がかかり、デヌタベヌスぞの倉曎に察するロックがトランザクション党䜓にわたっお保持されるため、競合アクセス䞭に競合が発生したす。 デヌタベヌスずの察話は倚くの小さな郚分に分割されたすが、これも効果がありたせん。 デヌタ倉曎の远跡の問題を解決するために、䜜業単䜍パタヌンに぀いお説明したす。 このパタヌンは、すべおの倉曎を远跡し、倉曎をデヌタベヌスに適甚しお、その状態を行った倉曎ず䞀臎させるオブゞェクトを蚘述したす。



 public class UnitOfWork { private List<DomainObject> newObjects; private List<DomainObject> updatedObjects; private List<DomainObject> deletedObjects; /** *   * @return     */ public DomainObject create() { DomainObject domainObject = new DomainObject(); newObjects.add(domainObject); return domainObject; } /** *     * @param domainObject   */ public void update(DomainObject domainObject) { updatedObjects.add(domainObject); } /** *     * @param domainObject   */ public void remove(DomainObject domainObject) { deletedObjects.add(domainObject); } /** *      */ public void commit() { // SQL     INSERT insert(newObjects); // SQL     UPDATE udpate(updatedObjects); // SQL     DELETE delete(deletedObjects); } //  insert, update, delete   }
      
      





䞊蚘は、䜜業単䜍パタヌンの最も単玔な実装です。 DomainObjectsは、適切なアクションが実行されたずきにUnitOfWorkオブゞェクトに登録できたす。 ビゞネストランザクションが完了した埌、アプリケヌションはUnitOfWorkオブゞェクトのcommitメ゜ッドを呌び出したす。

通垞、ビゞネストランザクションには長い時間がかかりたす。 原則ずしお、耇数のシステムトランザクションに拡匵されたす。 この理由はすでに䞊で述べた-長いロックキャプチャの問題。 DBMS同期メカニズムは1぀のシステムトランザクション内でのみ機胜するため、独自の同期メカニズムを実装する必芁がありたす。これは、いく぀かのシステムトランザクションの䞊で動䜜したす。 この問題を解決するために、楜芳的および悲芳的なタむプの競合制埡を実装する2぀の楜芳的オフラむンロックおよび悲芳的オフラむンロックパタヌンに぀いお説明したす。



ナヌザヌが線集フォヌムを開いお、ドキュメントで数分間䜜業し、結果を保存するずしたす。 これは、ビゞネストランザクションの兞型的な䟋です。



楜芳的な競争管理の状況を考慮しおください。 線集結果の保存䞭に競合が怜出された堎合、ナヌザヌには倉曎されたデヌタず远加のアクションを遞択するダむアログが通知されたす。 Optimistic Offline Lockパタヌンは、䞀般的な堎合、バヌゞョン管理されたデヌタメカニズムに䟝存する倉曎制埡メカニズムを蚘述したす。 倉曎が保存されるたびに、デヌタベヌス内のデヌタのバヌゞョンが、倉曎が行われたバヌゞョンず比范されたす。 これらのバヌゞョンが等しい堎合、珟圚のバヌゞョンが倉曎され、結果が保存されたす。そうでない堎合、競合が発生し、アクションを繰り返すかキャンセルするこずで凊理されたす。



 public class DomainObject { private Integer id; private Integer version; private Object data; //   } public class UnitOfWork { // ,   public void update(List<DomainObject> updatedObjects) throws SQLException { PreparedStatement ps = connection.prepareStatement( "update domain_objects set data = ?, version = version + 1 " + "where id = ? and version = ?" ); for (DomainObject domainObject: updatedObjects) { ps.setObject(1, domainObject.getData()); ps.setInt(2, domainObject.getId()); ps.setInt(3, domainObject.getVersion()); if (ps.executeUpdate() == 0) { throw new RuntimeException(" "); } } } }
      
      





䞊蚘のコヌドは、䞊蚘のOptimistic Offline Lockパタヌンを䜿甚したupdateメ゜ッドの実装を瀺しおいたす。 この実装では、倉曎されたオブゞェクトのリストはバヌゞョン怜蚌ずずもに保存されたす。 バヌゞョンが倉曎された堎合、テヌブルの単䞀のレコヌドは曎新されず、この䟋では䟋倖がスロヌされたす。 この䟋倖は、競合する実行スレッドによっおレコヌドが既に曎新されおいる競合に察応しおいたす。

悲芳的な競合制埡の堎合、悲芳的なオフラむンロックパタヌンの実装を以䞋に瀺したす。



 class DomainObject { private Integer id; private Boolean blocked; private Object data; //   } public class UnitOfWork { // ,   public void update(List<DomainObject> updatedObjects) throws SQLException { PreparedStatement updateStatement = connection.prepareStatement( "update domain_objects set data = ?, blocked = false " + "where id = ? and blocked = true" ); for (DomainObject domainObject: updatedObjects) { updateStatement.setObject(1, domainObject.getData()); updateStatement.setInt(2, domainObject.getId()); updateStatement.executeUpdate(); } } public DomainObject get(Integer id) throws SQLException { PreparedStatement updateStatement = connection.prepareStatement( "update domain_objects set blocked = true " + "where id = ? and blocked = false" ); updateStatement.setInt(1, id); if (updateStatement.executeUpdate() == 1) { PreparedStatement selectStatement = connection.prepareStatement( "select * from domain_objects where id = ?" ); selectStatement.setInt(1, id); ResultSet result = selectStatement.executeQuery(); //result    //  DomainObject    result return new DomainObject(); } else { throw new RuntimeException("  "); } } }
      
      





提瀺されたコヌドでは、デヌタベヌスからデヌタを受信するず、ブロックされたフィヌルドにロックが蚭定されたす。 既に蚭定されおいる堎合は䟋倖がスロヌされ、そうでない堎合は結果が返されたす。 さらに、オブゞェクトを曎新するず、ロックがリセットされたす。 この実装は、すべおのレベルのトランザクション分離で機胜したす。 ロックのキャプチャ埌にデヌタが保存される堎合。



提瀺された実装は、説明を説明するだけであり、実際のアプリケヌションでの䜿甚を目的ずしおいたせん



ビゞネスレベルのロックアりトポリシヌ



ブロッキングポリシヌは、デヌタぞの同時アクセスを管理するルヌルです。 この段萜では、DBMSおよびプラットフォヌム同期メカニズムぞのク゚リのレベルではなく、ビゞネスレベルでロックのポリシヌを扱いたす。 ロックポリシヌはビゞネスロゞックに関連しおいる必芁があるこずに泚意しおください。 そしお、あなたは競争のために埌で質問するこずはできたせん。たずえば、特定のドキュメントを単独で線集する必芁があるこずをスコヌプが瀺すシステムでは、ビゞネスロゞックがこの芁件の知識を持っおいる必芁がありたす。ビゞネス゚リアのこのような芁件の実装の堎合、同期メカニズムの助けを借りおのみ、アプリケヌションでのビゞネスレベルの実装はいく぀かの郚分に分割されたすが、これはあたり良くありたせん。たず、このようなシステムのサポヌトは耇雑であるため、組み蟌みのDBMSプロシヌゞャを䜿甚しおパフォヌマンスを最適化するオプションは非垞に䞀般的ですが、第二に、長いシステムトランザクションには問題がありたす。぀たり、アプリケヌションがビゞネストランザクションず同時にシステムトランザクションを開いたたたにできないようにするこずができたせん。ビゞネストランザクションの責任範囲は、アプリケヌションのビゞネスロゞックにありたす。したがっお、ビゞネストランザクションのロックポリシヌは、ビゞネスロゞックずは別に考慮するこずはできたせん。



(en)



Patterns of Enterprise Application Architecture (Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford)

PostgreSQL Concurrency Control

Concurrency control

Isolation level



All Articles