MySQLのデッドロックに対処する方法

バックグラウンド

一个死锁在MySQL发生在两个或多个事务相互持有和锁请求,创建依赖的循环。在交易系统中,死锁是生活中不可或缺的事实,并非完全可以避免的。

フロントリンクに書き込む
来自percona官方文档https://www.percona.com/blog/2014/10/28/how-to-deal-with-mysql-deadlocks/

Mysql5.6

只能使用SHOW ENGINE INNODB STATUS命令查看最新的死锁。
但是,使用Percona Toolkit的pt-deadlock-logger,
您可以按给定的时间间隔从SHOW ENGINE INNODB STATUS中检索死锁信息,
并将其保存到文件或表中以进行后期诊断。

MySQL 5.6では、新しい変数innodb_print_all_deadlocksを有効にして、InnoDBのすべてのデッドロックがmysqldエラーログに記録されるようにすることができます。
(5.7.26デフォルトオフ)

在进行所有诊断之前,最重要的做法是让应用程序捕获死锁错误(MySQL错误编号1213)并通过重试事务进行处理。

MySQLのデッドロックを診断する方法

show engine innodbstatusの制限

2つのトランザクションで実行された最後のステートメントのみを表示します

MySQL死锁可能涉及两个以上的事务,但是“最新检测到的DEADLOCK”部分仅显示最后两个事务。此外,它仅显示在两个事务中执行的最后一条语句,并锁定创建周期的两个事务的锁。缺少的是可能已经真正获得了锁的早期语句。我将展示一些有关如何收集遗漏语句的提示。

1 141013 6:06:22
2 *** (1) TRANSACTION:
3 TRANSACTION 876726B90, ACTIVE 7 sec setting auto-inc lock
4 mysql tables in use 1, locked 1
5 LOCK WAIT 9 lock struct(s), heap size 1248, 4 row lock(s), undo log entries 4
6 MySQL thread id 155118366, OS thread handle 0x7f59e638a700, query id 87987781416 localhost msandbox update
7 INSERT INTO t1 (col1, col2, col3, col4) values (10, 20, 30, 'hello')
8 *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
9 TABLE LOCK table `mydb`.`t1` trx id 876726B90 lock mode AUTO-INC waiting
10 *** (2) TRANSACTION:
11 TRANSACTION 876725B2D, ACTIVE 9 sec inserting
12 mysql tables in use 1, locked 1
13 876 lock struct(s), heap size 80312, 1022 row lock(s), undo log entries 1002
14 MySQL thread id 155097580, OS thread handle 0x7f585be79700, query id 87987761732 localhost msandbox update
15 INSERT INTO t1 (col1, col2, col3, col4) values (7, 86, 62, "a lot of things"), (7, 76, 62, "many more")
16 *** (2) HOLDS THE LOCK(S):
17 TABLE LOCK table `mydb`.`t1` trx id 876725B2D lock mode AUTO-INC
18 *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
19 RECORD LOCKS space id 44917 page no 529635 n bits 112 index `PRIMARY` of table `mydb`.`t2` trx id 876725B2D lock mode S locks rec but not gap waiting
20 *** WE ROLL BACK TRANSACTION (1)

構文解析

1行目は、デッドロックが発生した時刻を示しています。アプリケーションコードが発生するはずのデッドロックエラーをキャプチャしてログに記録する場合は、このタイムスタンプをアプリケーションログのデッドロックエラータイムスタンプと一致させることができます。ロールバックされたトランザクションがあります。そこから、トランザクション内のすべてのステートメントを取得します。

3行目と11行目は、トランザクション番号とアクティベーション時間を記録しています。SHOW ENGINE INNODB STATUSの出力を定期的に記録する場合(これは良い習慣です)、同じトランザクションからのステートメントがさらに表示されることを期待して、トランザクション番号を使用して前の出力を検索できます。ACTIVE秒は、トランザクションが単一のステートメントであるか複数のステートメントであるかを示すことができます。

4行目と12行目では、使用およびロックされているテーブルは現在のステートメント専用です。したがって、1つのテーブルを使用しても、トランザクションに1つのテーブルしか含まれないとは限りません。

5行目と13行目は、トランザクションが行った変更の数、つまり「ログエントリの取り消し」と、トランザクションが保持する行ロックの数、つまり「行ロック」を示しているため、言及する価値があります。この情報は、トランザクションの複雑さを意味します。

6行目と14行目は、スレッドID、接続ホスト、接続ユーザーを記録しています。別の良い習慣である、異なるアプリケーション機能に異なるMySQLユーザーを使用する場合、接続されたホストとユーザーに基づいて、トランザクションがどのアプリケーション領域からのものであるかを判別できます。

9行目では、最初のトランザクションについて、待機しているロック、この場合はテーブルt1のAUTO-INCロックのみが表示されます。他の可能な値は、S(共有ロックの場合)およびX(排他ロックの場合)です。

16行目と17行目は、2番目のトランザクションで保持しているロックを示しています。この場合、トランザクション(1)を待機しているのはAUTO-INCロックです。

18行目と19行目は、TRANSACTION(2)が待機しているロックを示しています。この場合、ギャップレコードロックではなく、別のテーブルの主キーの共有です。InnoDBには共有レコードロックのソースがほとんどありません:
1)共有モードでSELECT ... LOCKを使用します
2)外部キー参照レコードを使用します
3)ソーステーブルの共有ロックであるINSERT INTO ... SELECT
現在のtrx(2 )ステートメントはテーブルt1用です。単純な挿入であるため、1と3が削除されます。SHOW CREATE TABLE t1をチェックすることにより、Sロックが親テーブルt2の外部キー制約によるものであることを確認できます。

例2:MySQLコミュニティバージョンでは、各レコードロックにレコードコンテンツが出力されています。


1 2014-10-11 10:41:12 7f6f912d7700
2 *** (1) TRANSACTION:
3 TRANSACTION 2164000, ACTIVE 27 sec starting index read
4 mysql tables in use 1, locked 1
5 LOCK WAIT 3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1
6 MySQL thread id 9, OS thread handle 0x7f6f91296700, query id 87 localhost ro ot updating
7 update t1 set name = 'b' where id = 3
8 *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
9 RECORD LOCKS space id 1704 page no 3 n bits 72 index `PRIMARY` of table `tes t`.`t1` trx id 2164000 lock_mode X locks rec but not gap waiting
10 Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bit s 0
11 0: len 4; hex 80000003; asc ;;
12 1: len 6; hex 000000210521; asc ! !;;
13 2: len 7; hex 180000122117cb; asc ! ;;
14 3: len 4; hex 80000008; asc ;;
15 4: len 1; hex 63; asc c;;
16
17 *** (2) TRANSACTION:
18 TRANSACTION 2164001, ACTIVE 18 sec starting index read
19 mysql tables in use 1, locked 1
20 3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1
21 MySQL thread id 10, OS thread handle 0x7f6f912d7700, query id 88 localhost r oot updating
22 update t1 set name = 'c' where id = 2
23 *** (2) HOLDS THE LOCK(S):
24 RECORD LOCKS space id 1704 page no 3 n bits 72 index `PRIMARY` of table `tes t`.`t1` trx id 2164001 lock_mode X locks rec but not gap
25 Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bit s 0
26 0: len 4; hex 80000003; asc ;;
27 1: len 6; hex 000000210521; asc ! !;;
28 2: len 7; hex 180000122117cb; asc ! ;;
29 3: len 4; hex 80000008; asc ;;
30 4: len 1; hex 63; asc c;;
31
32 *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
33 RECORD LOCKS space id 1704 page no 3 n bits 72 index `PRIMARY` of table `tes t`.`t1` trx id 2164001 lock_mode X locks rec but not gap waiting
34 Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bit s 0
35 0: len 4; hex 80000002; asc ;;
36 1: len 6; hex 000000210520; asc ! ;;
37 2: len 7; hex 17000001c510f5; asc ;;
38 3: len 4; hex 80000009; asc ;;
39 4: len 1; hex 62; asc b;;

9行目と10行目:「スペースID」はテーブルスペースIDであり、「ページ番号」はレコードロックがテーブルスペースに配置されているページを示します。「Nビット」はページオフセットではなく、ロックされたビットマップのビット数です。ページオフセットは、10行目の「ヒープ番号」です。

11〜15行目:記録データを16進数で表示します。フィールド0はクラスターインデックス(主キー)です。最上位ビットを無視します。値は3です。フィールド1は、レコードを最後に変更したトランザクションのトランザクションIDであり、10進値は2164001で、これはトランザクション(2)です。フィールド2はロールバックポインタです。フィールド3から始まるのは、残りの行データです。フィールド3は、値が8の整数列です。フィールド4は、文字「c」の文字列列です。データを読み取ることにより、ロックされている行と現在の値が正確にわかります。

分析から他に何を学ぶことができますか?

ほとんどのMySQLデッドロックは2つのトランザクション間で発生するため、この仮定に基づいて分析を開始できます。例1では、trx(2)は共有ロックを待機しているため、trx(1)はテーブルt2の主キーレコードで共有ロックまたはミューテックスロックを保持します。col2が外部キー列であると仮定すると、trx(1)の現在のステートメントをチェックすることにより、同じレコードロックが必要ないことがわかります。したがって、trx(1)の前のステートメントにはSまたはXロックが必要である必要があります。 (s)t2のPKレコードについて。Trx(1)は7秒で4行しか変更しませんでした。次に、trx(1)のいくつかの特性を学習しました。多くの処理を行いましたが、いくつかの変更を加えました。変更にはテーブルt1とt2が含まれ、単一のレコードがt2に挿入されました。この情報を他のデータと組み合わせると、開発者はトランザクションを見つけるのに役立ちます。

以前の取引明細書は他にどこにありますか?

以下是提取事务历史记录的辅助查询,其中<PROCESSID>是有问题的连接的ID。

SELECT * FROM performance_schema.events_statements_history 
WHERE thread_id = (SELECT THREAD_ID FROM performance_schema.threads 
WHERE PROCESSLIST_ID = <PROCESSID>) \G

您可以在此处找到events_statements_history表的更多详细信息。

アプリケーションログと以前のSHOWENGINE INNODB STATUS出力に加えて、binlog、slowログ、通常のクエリログを使用することもできます。binlogの場合、binlog_format =ステートメントの場合、各binlogイベントにはthread_idがあります。コミットされたトランザクションのみがbinlogに記録されるため、binlogにはTrx(2)しか見つかりません。例1の場合、デッドロックがいつ発生したかがわかり、Trx(2)が9秒前に開始されたことがわかります。正しいbinlogファイルでmysqlbinlogを実行し、ステートメントthread_id = 155097580を探すことができます。次に、確認のためにこれらのステートメントをアプリケーションコードと相互参照することは、常に良いことです。


$ mysqlbinlog -vvv --start-datetime=“2014-10-13 6:06:12” --
stop-datatime=2014-10-13 6:06:22” 
mysql-bin.000010 > binlog_1013_0606.out

使用Percona Server 5.5及更高版本,您可以设置log_slow_verbosity在慢日志中包括InnoDB事务ID。然后,如果long_query_time = 0,则可以捕获所有语句,包括回滚到慢日志文件中的语句。在常规查询日志中,包含线程ID,该线程ID可用于查找相关语句。

MySQLのデッドロックを回避する方法

アプリケーションを変更します。場合によっては、長いトランザクションを小さなトランザクションに分割することでデッドロックの発生を大幅に減らすことができるため、ロックがより早く解放されます。その他の場合、デッドロックが増加する原因は、2つのトランザクションが1つ以上のテーブルの同じデータセットに異なる順序でアクセスすることです。次に、同じ順序でデータにアクセスするように変更します。つまり、アクセスをシリアル化します。このように、トランザクションが同時に発生すると、デッドロックではなくロック待機が発生します。

外部キー制約を削除して2つのテーブルを分離したり、インデックスを追加してスキャンとロックされた行を最小限に抑えるなど、テーブルスキーマを変更します。

断続的なロックの場合、トランザクション分離レベルを変更して、コミットされたセッションまたはトランザクションを読み取り、それを回避できます。ただし、セッションまたはトランザクションのバイナリログ形式はROWまたはMIXEDである必要があります。(RC +行はギャップロックギャップを回避します)

文章来自percona官网2014年,需要我们自行测试

この記事では、主な技術コンテンツは、インターネットテクノロジーの巨人の共有と、一部の自己処理(注釈の役割のみ)に由来することを説明しています。関連する質問がある場合は、確認後にメッセージを残してください。侵害の実装は削除されます

おすすめ

転載: blog.csdn.net/baidu_34007305/article/details/111269727