ホームページ >データベース >mysql チュートリアル >MySQL デッドロックのトラブルシューティングの分析例

MySQL デッドロックのトラブルシューティングの分析例

WBOY
WBOY転載
2023-05-29 12:22:061281ブラウズ

問題が発生します

# ある日の午後、突然システムが警告を発し、例外をスローしました:

よく見るとトランザクションのロールバック例外のようでした。デッドロックのためロールバックされたと書かれていました。デッドロックの問題であることが判明しました。Mysql のロックについてはまだある程度理解しているので、この問題を積極的に調査し始めました。 。

まず、データベースで Innodb Status を検索します。最後のデッドロック情報が Innodb Status に記録されます。次のコマンドを入力します:

エンジンの INNODB ステータスを表示

デッドロック情報は次のとおりです。SQL 情報は単純に処理されています:

------------------------------------

最後に検出されたデッドロック

------------------------------------

2019-02-22 15:10:56 0x7eec2f468700

*** (1) トランザクション:

トランザクション 2660206487、アクティブ 0 秒開始インデックス read

mysql テーブルは使用中 1、ロックされています 1

ロック待機 2 つのロック構造体、ヒープ サイズ 1136、1 行のロック

# MySQL スレッド ID 31261312、OS スレッド ハンドル 139554322093824、クエリ ID 11624975750 10.23.134.92 erp_crm__6f73 更新中

/*id:3637ba36*/UPDATE tenant_config SET

オープンカードポイント = 0

ここで、テナント ID = 123

*** (1) このロックが許可されるのを待っています:

RECORD LOCKS スペース ID 1322 ページ番号 534 n ビット 960 テーブルのインデックス uidx_tenant ——erp_crm_member_plan——. ——tenant_config—— trx id 2660206487 lock_mode X は、rec をロックしますが、ギャップ待機はしません

*** (2) トランザクション:

トランザクション 2660206486、アクティブ 0 秒開始インデックス read

mysql テーブルは使用中 1、ロックされています 1

3 つのロック構造体、ヒープ サイズ 1136、2 つの行ロック

# MySQL スレッド ID 31261311、OS スレッド ハンドル 139552870532864、クエリ ID 11624975758 10.23.134.92 erp_crm__6f73 更新中

/*id:3637ba36*/UPDATE tenant_config SET

オープンカードポイント = 0

ここで、テナント ID = 123

*** (2) ロックを保持します (S):

RECORD LOCKS スペース ID 1322 ページ番号 534 n ビット 960 テーブルのインデックス uidx_tenant ——erp_crm_member_plan——. ——tenant_config—— trx id 2660206486 ロック モード S

*** (2) このロックが許可されるのを待っています:

RECORD LOCKS スペース ID 1322 ページ番号 534 n ビット 960 テーブルのインデックス uidx_tenant ——erp_crm_member_plan——. ——tenant_config—— trx id 2660206486 lock_mode X は、rec をロックしますが、ギャップ待機はしません

*** トランザクションをロールバックします (1)

----------------

このデッドロック ログを簡単に分析して説明します。トランザクション 1 が Update ステートメントを実行するとき、uidx_tenant インデックスを取得し、次に where 条件で X ロック (行ロック) を取得する必要があります。トランザクション 2 は同じ Update ステートメントを実行し、次のことも考えます。 uidx_tenant. X ロック (行ロック) を取得するためにデッドロックが発生し、トランザクション 1 がロールバックされました。当時私は非常に混乱していましたが、デッドロックが発生するための必要条件を思い出しました:

相互に排他的です。

条件をリクエストして保留します。

条件の剥奪はありません。

待機サイクル。ログを見ると、トランザクション 1 とトランザクション 2 が同じ行の行ロックを競合していることがわかります。これは、以前の周期的なロックの競合とは少し異なります。どう見ても満たすことができません。循環待機状態。同僚から注意を受けましたが、デッドロックログは調査できないため、業務コードと業務ログから問題を調査するしかありません。このコードのロジックは次のとおりです:

public int saveTenantConfig(PoiContext poiContext, TenantConfigDO tenantConfig) {

試す {###### return tenantConfigMapper.saveTenantConfig(poiContext.getTenantId(), poiContext.getPoiId(), tenantConfig);

} catch (DuplicateKeyException e) {

LOGGER.warn("[saveTenantConfig] 主キーの競合。レコードを更新します。context:{}, config:{}", poiContext, tenantConfig);

return tenantConfigMapper.updateTenantConfig(poiContext.getTenantId(), tenantConfig);

}

}

このコードの意味は、構成ファイルを保存することです。一意のインデックスの競合が発生した場合、ファイルは更新されます。もちろん、ここでの記述はあまり標準化されていない可能性があります。実際には、

を使用できます。 …

に挿入 重複キー更新時

同様の効果が得られますが、これを使用しても実際にはデッドロックが発生します。コードを読んだ後、同僚が当時の業務ログ

を送ってくれました。 同時に発生した 3 つのログがあることがわかります。これは、一意のインデックスの競合が発生して更新されたステートメントを入力し、その後デッドロックが発生したことを示しています。この時点で、ようやく答えが少し明確になったように思えます。

この時点で、テーブル構造を次のように見てみましょう (簡略化):

テーブルの作成 ——tenant_config—— (

——id—— bigint(21) NOT NULL AUTO_INCREMENT,

——tenant_id—— int(11) NOT NULL,

——open_card_point—— int(11) デフォルト NULL,

主キー (——id——)、

一意のキー ——uidx_tenant—— (——tenant_id——)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT

tenant_id は一意のインデックスとして使用され、挿入と更新の where 条件はすべて一意のインデックスに基づいています。

tenant_config SET

を更新します オープンカードポイント = 0

ここで、テナント ID = 123

この時点では挿入時のユニークインデックスのロックが関係しているような気がしますので、次のステップで詳しく分析してみましょう。

詳細な分析

上では 3 つのトランザクションが更新ステートメントに入ると言われましたが、説明を簡単にするため、同時に更新ステートメントに入るには 2 つのトランザクションだけが必要です。次の表は、発生プロセス全体を示しています:

ヒント: S ロックは共有ロック、X ロックは相互排他ロックです。一般に、X ロック、S ロック、および X ロックは相互排他的ですが、S ロックと S ロックは相互排他的ではありません。

上記のプロセスから、このデッドロックの鍵は S ロックの取得であることがわかりますが、なぜ再挿入時に S ロックを取得する必要があるのでしょうか?一意のインデックスを検出する必要があるためですか? RR 分離レベルでは、読み取りたい場合は現在の読み取りとなるため、実際には S ロックを追加する必要があります。ここで、ユニークキーが既に存在していることが分かり、このとき、2つのトランザクションのSロックにより更新の実行がブロックされ、上記のループ待ち状態が形成される。

ヒント: MVCC では、現在の読み取りとスナップショットの読み取りの違い: 最新のデータを取得するには、現在の読み取りは毎回ロックする必要があります (共有ロックまたはミューテックス ロックを使用できます)。一方、スナップショットの読み取りはこのトランザクションの開始を読み取ります。スナップショットは元に戻すログを通じて実装されました。

これがデッドロック全体の原因です。この種のデッドロックが発生するもう 1 つの状況は、同時に 3 つの挿入操作がある場合です。最初に挿入されたトランザクションが最後にロールバックされると、これは他の 2 つの操作でも発生します。トランザクションのデッドロック。

解決###### ここでの中心的な問題は、S ロックを取り除くことです。参考までに 3 つの解決策を示します:

RR 分離レベルを RC 分離レベルまで下げます。ここでは、RC 分離レベルはスナップショット読み取りを使用するため、S ロックは追加されません。

再度挿入する場合は、select * for updateを使用してXロックを追加すると、Sロックが追加されなくなります。

分散ロックは事前に追加することも、Redis や ZK などを使用することもできます。分散ロックについては、私のこの記事を参照してください。分散ロックについて話しましょう

最初の方法は、分離レベルを簡単に変更できないため、あまり現実的ではありません。 3 番目の方法はさらに面倒です。そこで最終的に落ち着いたのが 2 番目の方法です。

以上がMySQL デッドロックのトラブルシューティングの分析例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。