mysqlの行ロック
デッドロックを分析する前に、MySQLの行ロックを思い出してみましょう。MySQLには次の3種類の行ロックがあります。このセクションではそれらを簡単に紹介します。それらについて詳しく知りたい場合は、他の記事を参照してください。
レコードロック:単一のレコードをロックし
ますギャップロック:レコードの前のギャップをロックし、レコードを挿入できないようにします
Next-key Lock:データとデータの前のギャップを同時にロックします。つまり、データもデータの前のギャップもレコードの挿入を許可されません。
挿入意図ギャップロック(意図ロックの挿入)
単純な挿入は、挿入された行に対応するインデックスレコードに排他ロックを追加します。これはレコードロックであり、ギャップがないため、他のセッションがギャップにレコードを挿入するのをブロックしません。
ただし、挿入操作の前にロックが追加されます。公式文書では、意図的なギャップロックである挿入意図ギャップロックと呼ばれています。この意図的なギャップロックの目的は、複数のトランザクションが同じギャップに同時に挿入された場合、挿入されたレコードがギャップ内の同じ位置にない限り、他のセッションを待たずに完了することができることを示すことです。挿入操作では、実際のギャップロックを追加する必要はありません。
ギャップロックを使用すると、挿入の同時実行性が低下します
デッドロックのトラブルシューティング方法は?
デッドロックの条件
- 相互排除、共有リソースXおよびYは1つのスレッドのみが占有できます
- 占有して待機します。スレッドT1は共有リソースXを取得し、共有リソースYを待機している間は共有リソースXを解放しません。
- プリエンプションなし、他のスレッドはスレッドT1によって占有されているリソースを強制的にプリエンプションできません
- 循環待機、スレッドT1はスレッドT2によって占有されているリソースを待機し、スレッドT2はスレッドT1によって占有されているリソースを待機します。これは循環待機です。
最初にデッドロックの例を作成し、デッドロックの基本的なトラブルシューティングのアイデアを示しましょう。
CREATE TABLE `order_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单编号',
`status` tinyint(3) NOT NULL COMMENT '订单状态',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='订单表';
insert into order_info (id, status) values (1, 0), (2, 0);
時間 | クライアントA | クライアントB |
---|---|---|
T1 | 始める; order_info set status = 1を更新します。ここで、id = 1; |
|
T2 | 始める; order_info set status = 2を更新します。ここで、id = 2; |
|
T3 | order_info set status = 1を更新します。ここで、id = 2; | |
T4 | order_info set status = 2を更新します。ここで、id=1; デッドロックロールバック |
|
T1 | 専念; |
デッドロックが発生すると、mysqlはロールするオーバーヘッドが最小のトランザクションを選択するため、別のトランザクションを正常に実行できます。
次のステートメントを実行して、最新のデッドロックレコードを取得します
show engine innodb status
デッドロックログから、以下の有用な情報を分析できます。
次のステートメントは、トランザクション1がデッドロックしたときに実行されます。
update order_info set status = 1 where id = 2;
次のステートメントは、トランザクション2がデッドロックしたときに実行されます。
update order_info set status = 2 where id = 1;
また、トランザクション1を待機しているロックは、トランザクション2によって取得されたロックとまったく同じであり、トランザクション2を待機しているロックがトランザクション1によって取得されたことは確かです。
この時点で、デッドロックの条件が破棄されている限り、ビジネスコード、デッドロックがどのように生成されるかを確認する必要があります。
たとえば、この例のデッドロックを回避するには、循環待機の状態を破棄し、リソースを順番に更新するだけです(つまり、複数のリソースを更新する場合、それらはIDに従って小さいものから大きいものに更新されます)。
実稼働環境は行き詰まっています
皆さんの印象を深めるために、以前に本番環境で発生したデッドロックの問題について話しましょう(もちろん、トランザクションで実行されるSQLが多いため、実際の状況はそれほど簡単にトラブルシューティングすることはできません。したがって、これはテストです。問題をすばやく見つけるスキル)
次の2つのテーブルがあります。アカウントレコードを挿入します
CREATE TABLE `balance_account` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '账户编号',
`member_code` varchar(50) NOT NULL COMMENT '用户编号',
`balance` bigint(20) NOT NULL COMMENT '账户余额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='账户表';
CREATE TABLE `balance_charger` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`member_code` varchar(50) NOT NULL COMMENT '用户编号',
`charger_num` varchar(50) NOT NULL COMMENT '流水单据号',
`status` tinyint(3) NOT NULL COMMENT '流水状态,0初始状态,1完成',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=latin1 COMMENT='账户流水表';
insert into balance_account(id, member_code, balance) value (1, 'a', 0);
ユーザーアカウントのお金が変わると、これら2つのテーブルを順番に操作する必要があります
// 插入流水
insert balance_charger;
// 更新账户
update balance_account;
// 更新流水
update balance_charger;
時間 | クライアントA(タブA) | クライアントB(タブB) |
---|---|---|
T1 | 始める; balance_charger(member_code、charger_num、 `status`)の値('a'、'v1'、0);に挿入します。 更新balance_accountsetbalance = balance + 100ここで、id = 1; |
|
T2 | 始める; balance_charger(member_code、charger_num、 `status`)の値('a'、'v2'、0);に挿入します。 更新balance_accountsetbalance = balance + 100ここで、id = 1; |
|
T3 | balance_charger set status = 1を更新します。ここで、charger_num ='v1'; | |
T4 | デッドロックロールバック | |
T5 | 専念; |
このデッドロックはどのようにして発生したのですか?
updateステートメントのwhere条件でインデックスが使用されていない場合、テーブル全体のスキャンが実行され、テーブル全体をロックするのと同じように、テーブル全体のレコードにNext-keyLockが追加されます。
それを解決する方法は?
非常に簡単です。where条件の列にインデックスを追加するだけです。
ALTER TABLE balance_charger ADD INDEX idx_charger_num(`charger_num`)
リファレンスブログ
[1] https://mp.weixin.qq.com/s/B_slzAZLp-y8G0haZwIbTg
[2] https://bbs.huaweicloud.com/blogs/300169
插入意向锁
[3]https://www.cnblogs。 com / better-farther-world2099 / articles / 14722850.html
[4] https://juejin.cn/post/6844903666856493064