Mar*_*oni 5 mysql mariadb mariadb-10.1
我们使用 MariaDB 10.1.19 托管旧数据库。我们有一个lpr
表(在 InnoDB 上),我们想在其中从文本列移动gate
到规范化外键gate_id
。我们的代码并行执行 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]%'
...
Run Code Online (Sandbox Code Playgroud)
由于受所有 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
在更新期间执行 a不会显示任何锁定。
我们做错了什么?即使使用 InnoDB 也有表级锁吗?或者 UPDATE 锁定的行是否比 WHERE 子句严格选择的行多?
我们做错了什么?即使使用 InnoDB 也有表级锁吗?
您的问题是缺少合适的索引来使用。InnoDB 执行next-key 锁定,这意味着它只会锁定它将更新的行,而且还会使用查找索引锁定中间的间隙。因为没有合适的索引可用于给定的过滤器,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
Run Code Online (Sandbox Code Playgroud)
在另一个会话中:
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;;
Run Code Online (Sandbox Code Playgroud)
您有多种选择:
通过放宽事务隔离级别来提高并发性(但可能会发生鬼读):
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
Run Code Online (Sandbox Code Playgroud)
在另一个会话上,现在会话成功:
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
Run Code Online (Sandbox Code Playgroud)改进您的设计和/或查询,以便您只使用正确索引的查询来提高并发性。
更多关于并发和间隙锁定:https : //www.percona.com/blog/2012/03/27/innodbs-gap-locks/