遅く、私は多くの行ロック競合に直面しています。競合しているテーブルは特定のテーブルのようです。
これは一般的に何が起こるかです-
5分以内にフロントエンドが応答しないようです。セッションをチェックすると、行ロックの競合が表示されます。誰もが投げかける「解決策」は、セッションを強制終了することです:/
データベース開発者として
この質問が自由回答/不十分な情報であると思われる場合は、自由に編集/知らせてください-いくつかの追加情報を追加できるように最善を尽くします。
問題のテーブルは多くの挿入と更新の下にあります、私はそれが最も忙しいテーブルの1つだと思います。 SPはかなり複雑です-簡単にするために-さまざまなテーブルからデータをフェッチし、それをワークテーブルに入力します。多くの算術演算がワークテーブルで発生し、ワークテーブルの結果が挿入されます/問題の表に更新されました。
データベースのバージョンは、Oracle Database 10g Enterprise Editionリリース10.2.0.1.0-64ビットです。ロジックのフローは両方のセッションで同じ順序で実行され、トランザクションは長時間開いたままになりません(または少なくともIthinkso)。 、およびロックはトランザクションのアクティブな実行中に発生します。
pdate:テーブルの行数が予想よりも多く、約310万行です。また、セッションをトレースした後、このテーブルに対するいくつかの更新ステートメントがインデックスを利用していないことがわかりました。なぜそうなのか、よくわかりません。 where句で参照される列にはインデックスが付けられます。現在、インデックスを再構築しています。
ストアドプロシージャのどの行がこれらの行ロック競合を引き起こしているのかを見つけることは可能でしょうか?
正確ではありませんが、ロックを引き起こしているSQLステートメントを取得して、プロシージャ内の関連する行を特定できます。
_SELECT sid, sql_text
FROM v$session s
LEFT JOIN v$sql q ON q.sql_id=s.sql_id
WHERE state = 'WAITING' AND wait_class != 'Idle'
AND event = 'enq: TX - row lock contention';
_
コーディングに関するこのような問題を軽減/回避/排除するための一般的なガイドラインは何でしょうか?
ロックに関するOracleコンセプトガイドのセクション は、「行はライターによって変更された場合にのみロックされる」と述べています。同じ行を更新する別のセッションは、最初のセッションがCOMMIT
またはROLLBACK
になるまで待機してから続行できます。問題を解消するためにユーザーをシリアル化することができますが、問題を問題ではないレベルまで減らすことができるいくつかの方法を次に示します。
COMMIT
より頻繁に。すべてのCOMMIT
はロックを解放するため、更新をバッチで実行できる場合は、別のセッションが同じ行を必要とする可能性が低くなります。UPDATE t1 SET f1=DECODE(f2,’a’,f1+1,f1);
は、より選択的な(より少ないロックを読み取る)_UPDATE t1 SET f1=f1+1 WHERE f2=’a’;
_として書き換える必要があります。もちろん、ステートメントを変更してもテーブル内の大部分の行がロックされる場合は、変更によって読みやすさが向上します。BULK COLLECT ... FORALL
_ で一括処理を使用できます。UPDATE
とCOMMIT
の間で行われる作業を減らします。たとえば、更新のたびにコードがメールを送信する場合は、メールをキューに入れ、更新をコミットした後に送信することを検討してください。SELECT ... FOR UPDATE NOWAIT
_または_WAIT 2
_を実行して、待機を処理するようにアプリケーションを設計します。次に、行をロックできないことをキャッチし、別のセッションが同じデータを変更していることをユーザーに通知できます。開発者の立場から回答をさせていただきます。
私の意見では、説明したような行の競合が発生した場合、それはアプリケーションにバグがあるためです。ほとんどの場合、このタイプの競合は、更新が失われる脆弱性の兆候です。これは AskTomのスレッド が失われた更新の概念を説明しています。
更新が失われるのは、次の場合です。
セッション1:トムの従業員レコードを読み取る
セッション2:トムの従業員レコードを読み取る
セッション1:トムの従業員レコードを更新する
セッション2:トムの従業員レコードを更新する
セッション2は、セッション1の変更を確認せずに上書きし、更新が失われます。
更新が失われたことによる厄介な副作用が発生しました。セッション1がまだコミットされていないため、セッション2がブロックされる可能性があります。ただし、主な問題は、セッション2が盲目的にレコードを更新することです。両方のセッションがステートメントを発行するとします。
UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk
両方のステートメントの後、行がセッション1によって変更されたことがセッション2に通知されずに、セッション1の変更が上書きされました。
失われた更新(および競合の副作用)は決して発生してはなりません。100%回避できます。ロックを使用するには、2つの主要な方法 楽観的および悲観的ロック でそれらを防止する必要があります。
行を更新したい。このモードでは、その行のロックを要求することにより、他のユーザーがこの行を変更できないようにします(SELECT ... FOR UPDATE NOWAIT
ステートメント)。行がすでに変更されている場合は、エラーメッセージが表示されます。このメッセージはエンドユーザーに適切に変換できます(この行は別のユーザーによって変更されています)。行が使用可能な場合は、変更(UPDATE)を行い、トランザクションが完了するたびにコミットします。
行を更新したい。ただし、その行のロックを維持したくない場合は、おそらく複数のトランザクションを使用して行を更新するため(Webベースのステートレスアプリケーション)、またはユーザーが長時間ロックを保持することを望まない場合があります(他の人がブロックされる可能性があります)。その場合、すぐにロックをリクエストすることはありません。マーカーを使用して、更新が発行されるときに行が変更されていないことを確認します。すべての列の値をキャッシュするか、自動的に更新されるタイムスタンプ列またはシーケンスベースの列を使用できます。選択内容が何であれ、更新を実行しようとしているときに、次のようなクエリを発行して、その行のマーカーが変更されていないことを確認します。
SELECT <...>
FROM table
WHERE id = :id
AND marker = :marker
FOR UPDATE NOWAIT
クエリが行を返す場合は、更新を行います。そうでない場合は、最後にクエリを実行してから行が変更されていることを意味します。プロセスを最初からやり直す必要があります。
注:DBにアクセスするすべてのアプリケーションを完全に信頼している場合は、楽観的ロックの直接更新を利用できます。あなたは直接発行することができます:
UPDATE table
SET <...>,
marker = marker + 1
WHERE id = :id;
ステートメントが行を更新しない場合、誰かがこの行を変更したため、最初からやり直す必要があります。
すべてのアプリケーションがこのスキームに同意する場合、他の誰かによってブロックされることはなく、ブラインドアップデートを回避します。ただし、行を事前にロックしない場合でも、別のアプリケーション、バッチジョブ、または直接更新が楽観的ロックを実装していない場合は、無期限にロックされる可能性があります。これが、ロックスキームの選択に関係なく、常に行をロックすることをお勧めする理由です(行をロックすると、rowidを含むすべての値を取得するため、パフォーマンスへの影響は無視できます)。
この答えはおそらく、The Daily WTFのエントリーに適格です。
右、セッションを追跡してUSER_SOURCE
を検索した後-根本的な原因を追跡しました