レガシーデータベースをホストするMariaDB 10.1.19を使用しています。テキスト列lpr
から正規化された外部キー_gate_id
_に移動するgate
テーブル(InnoDB上)があります。私たちのコードは、次のようにUPDATEを並行して実行しています。
_UPDATE lpr SET gate_id=1 WHERE gate_id IS null AND gate LIKE '%[1]%'
UPDATE lpr SET gate_id=2 WHERE gate_id IS null AND gate LIKE '%[2]%'
UPDATE lpr SET gate_id=3 WHERE gate_id IS null AND gate LIKE '%[3]%'
UPDATE lpr SET gate_id=4 WHERE gate_id IS null AND gate LIKE '%[4]%'
...
_
すべてのUPDATEの影響を受ける行は互いに素であり、テーブルがInnoDB上にあるため、ロックの競合は予想されませんが、次のエラーが発生します。
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
すべての更新。 _innodb_lock_wait_timeout
_を〜100に増やしても何も変わりません。また、_FOREIGN_KEY_CHECKS
_を0に設定しても変わりません。
gate
列に複数の_[VALUE]
_文字列が含まれることはありません。
手動で単一のUPDATEを実行しても、そのようなエラーは発生しません。
_SHOW FULL PROCESSLIST
_を実行しても、更新中にロックは表示されません。
私たちは何を間違っているのですか? InnoDBを使用している場合でも、テーブルレベルのロックはありますか?または、UPDATEはWHERE句で厳密に選択された行よりも多くの行をロックしますか?
何が悪いのでしょうか? InnoDBを使用している場合でも、テーブルレベルのロックはありますか?
あなたの問題は、使用する適切なインデックスがないことです。 InnoDBは next-key Locking を実行します。つまり、更新する行だけをロックしますが、ルックアップインデックスを使用してその間のギャップもロックします。指定されたフィルターに適切なインデックスを使用できないため、gate LIKE '%[1]%'
技術的にはテーブルロックを行いませんが、クエリプラン(すべての行のロック)に従ってすべての行ギャップにロックを設定します。
私はあなたの構造を再現しました、そしてSHOW ENGINE INNODB STATUS
は、必要なすべての情報を提供します。
mysql> create table lpr (gate_id int, gate varchar(10));
Query OK, 0 rows affected (0.21 sec)
mysql> insert into lpr values (1, 'wqer[1]sd');
Query OK, 1 row affected (0.11 sec)
mysql> insert into lpr values (2, 'wqer[2]sd');
Query OK, 1 row affected (0.06 sec)
mysql> insert into lpr values (3, 'wqer[2]sd');
Query OK, 1 row affected (0.03 sec)
mysql> insert into lpr values (4, 'wqer[4]sd');
Query OK, 1 row affected (0.05 sec)
mysql> insert into lpr values (5, 'wqer[5]sd');
Query OK, 1 row affected (0.09 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE lpr SET gate_id=1 WHERE gate_id IS null AND gate LIKE '%[1]%';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
mysql> SHOW ENGINE INNODB STATUS\G
...
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 4126, ACTIVE 8 sec
2 lock struct(s), heap size 1136, 6 row lock(s)
MySQL thread id 8, OS thread handle 140628280551168, query id 21 localhost root starting
SHOW ENGINE INNODB STATUS
別のセッションで:
mysql> UPDATE lpr SET gate_id=2 WHERE gate_id IS null AND gate LIKE '%[2]%'
-> ;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
---TRANSACTION 4127, ACTIVE 8 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 9, OS thread handle 140628236515072, query id 28 localhost root updating
UPDATE lpr SET gate_id=2 WHERE gate_id IS null AND gate LIKE '%[2]%'
------- TRX HAS BEEN WAITING 8 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4 page no 4 n bits 72 index GEN_CLUST_INDEX of table `enwiki`.`lpr` trx id 4127 lock_mode X waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 6; hex 000000000200; asc ;;
1: len 6; hex 000000001011; asc ;;
2: len 7; hex 82000001060110; asc ;;
3: len 4; hex 80000001; asc ;;
4: len 9; hex 777165725b315d7364; asc wqer[1]sd;;
いくつかのオプションがあります。
トランザクション分離レベルを緩和して同時実行性を高めます(ただし、ゴーストリードが発生する可能性があります)。
mysql> SET SESSION transaction_isolation='READ-COMMITTED';
Query OK, 0 rows affected (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE lpr SET gate_id=1 WHERE gate_id IS null AND gate LIKE '%[1]%';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
別のセッションでは、セッションは成功します。
mysql> SET SESSION transaction_isolation='READ-COMMITTED';
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE lpr SET gate_id=2 WHERE gate_id IS null AND gate LIKE '%[2]%'
-> ;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
同時実行性を向上させるために、適切にインデックス付けされたクエリのみを使用するように、デザインやクエリを改善します。
同時実行性とギャップロックの詳細: https://www.percona.com/blog/2012/03/27/innodbs-gap-locks/