MySQL的共享锁阻塞会话案例浅析输入日志标题
这是问题是一个网友遇到的问题:一个UPDATE语句产生的共享锁阻塞了其他会话的案例,对于这个案例,我进一步分析、总结和衍化了相关问题。下面分析如有不对的地方,敬请指正。下面是初始化环境和数据的脚本。
--
-- Table structure for table `tableA`
--
DROP TABLE IF EXISTS `tableA`;
CREATE TABLE `tableA` (
`id` varchar(10) NOT NULL,
`name` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
--
-- Dumping data for table `tableA`
--
LOCK TABLES `tableA` WRITE;
INSERT INTO `tableA` VALUES ('1','11'),('2','22');
UNLOCK TABLES;
--
-- Table structure for table `tableB`
--
DROP TABLE IF EXISTS `tableB`;
CREATE TABLE `tableB` (
`id` varchar(10) NOT NULL,
`bill_id` varchar(10) DEFAULT NULL,
`update_time` bigint(12) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_bill_id` (`bill_id`)
) ENGINE=InnoDB;
--
-- Dumping data for table `tableB`
--
LOCK TABLES `tableB` WRITE;
/*!40000 ALTER TABLE `tableB` DISABLE KEYS */;
INSERT INTO `tableB` VALUES ('100','1',1586880000000),('200','2',1586793600000),('300','2',1586880000000),('400','2',1586880000000),('500','3',1586990000000),('600','4' ,1586990000000);
/*!40000 ALTER TABLE `tableB` ENABLE KEYS */;
UNLOCK TABLES;
```
下面我们先通过实验模拟一下这个阻塞问题,事务的级别为默认的可重复读级别(Repeatable Read),如下所示:
实验环境: MySQL 5.6.25
会话1(连接ID=52)在autocommit=0下面,执行一个update语句
mysql> select connection_id() from dual;
+-----------------+
| connection_id() |
+-----------------+
| 52 |
+-----------------+
1 row in set (0.00 sec)
mysql> set session autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE tableA a
-> LEFT JOIN
-> (SELECT
-> bill_id,MAX(update_time)
-> FROM
-> tableB
-> GROUP BY bill_id) b ON a.id = b.bill_id
-> SET
-> a.name = 'abcd'
-> WHERE
-> a.id = '2';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql>
会话2(连接ID=54)执行一个delete语句被阻塞
mysql> select connection_id() from dual;
+-----------------+
| connection_id() |
+-----------------+
| 54 |
+-----------------+
1 row in set (0.00 sec)
mysql> delete from tableB where bill_id='1';
会话3中进行分析、查看这些阻塞、锁等相关信息,如下所示:
mysql> SELECT b.trx_mysql_thread_id AS 'blocked_thread_id'
-> ,b.trx_query AS 'blocked_sql_text'
-> ,c.trx_mysql_thread_id AS 'blocker_thread_id'
-> ,c.trx_query AS 'blocker_sql_text'
-> ,( Unix_timestamp() - Unix_timestamp(c.trx_started) )
-> AS 'blocked_time'
-> FROM information_schema.innodb_lock_waits a
-> INNER JOIN information_schema.innodb_trx b
-> ON a.requesting_trx_id = b.trx_id
-> INNER JOIN information_schema.innodb_trx c
-> ON a.blocking_trx_id = c.trx_id
-> WHERE ( Unix_timestamp() - Unix_timestamp(c.trx_started) ) > 4;
+-------------------+--------------------------------------+-------------------+------------------+--------------+
| blocked_thread_id | blocked_sql_text | blocker_thread_id | blocker_sql_text | blocked_time |
+-------------------+--------------------------------------+-------------------+------------------+--------------+
| 54 | delete from tableB where bill_id='1' | 52 | NULL | 39 |
+-------------------+--------------------------------------+-------------------+------------------+--------------+
1 row in set (0.01 sec)
mysql>
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX\G;
*************************** 1. row ***************************
trx_id: 1261156958
trx_state: LOCK WAIT
trx_started: 2020-09-21 07:05:36
trx_requested_lock_id: 1261156958:1678:4:2
trx_wait_started: 2020-09-21 07:05:36
trx_weight: 2
trx_mysql_thread_id: 54
trx_query: delete from tableB where bill_id='1'
trx_operation_state: starting index read
trx_tables_in_use: 1
trx_tables_locked: 1
trx_lock_structs: 2
trx_lock_memory_bytes: 360
trx_rows_locked: 1
trx_rows_modified: 0
trx_concurrency_tickets: 0
trx_isolation_level: REPEATABLE READ
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 10000
trx_is_read_only: 0
trx_autocommit_non_locking: 0
*************************** 2. row ***************************
trx_id: 1261156943
trx_state: RUNNING
trx_started: 2020-09-21 07:05:28
trx_requested_lock_id: NULL
trx_wait_started: NULL
trx_weight: 6
trx_mysql_thread_id: 52
trx_query: NULL
trx_operation_state: NULL
trx_tables_in_use: 0
trx_tables_locked: 0
trx_lock_structs: 5
trx_lock_memory_bytes: 1184
trx_rows_locked: 14
trx_rows_modified: 1
trx_concurrency_tickets: 0
trx_isolation_level: REPEATABLE READ
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 10000
trx_is_read_only: 0
trx_autocommit_non_locking: 0
2 rows in set (0.00 sec)
ERROR:
No query specified
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS\G;
*************************** 1. row ***************************
lock_id: 1261156958:1678:4:2
lock_trx_id: 1261156958
lock_mode: X
lock_type: RECORD
lock_table: `test`.`tableB`
lock_index: idx_bill_id
lock_space: 1678
lock_page: 4
lock_rec: 2
lock_data: '1', '100'
*************************** 2. row ***************************
lock_id: 1261156943:1678:4:2
lock_trx_id: 1261156943
lock_mode: S
lock_type: RECORD
lock_table: `test`.`tableB`
lock_index: idx_bill_id
lock_space: 1678
lock_page: 4
lock_rec: 2
lock_data: '1', '100'
2 rows in set (0.00 sec)
ERROR:
No query specified
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS\G
*************************** 1. row ***************************
requesting_trx_id: 1261156958
requested_lock_id: 1261156958:1678:4:2
blocking_trx_id: 1261156943
blocking_lock_id: 1261156943:1678:4:2
1 row in set (0.00 sec)
从上图的信息中,我们可以看出事务(trx_id=1261156958)处于等待状态,TRX_STATE是LOCK WAIT,表示当前事务事务正在等待锁资源的获取,通过lock_id,我们可以知道,事务在表空间ID为1678(即表tableB对应的表空间),页码值为4,堆号2的记录上加了共享锁,而恰巧事务(trx_id=1261156943)在这些记录上拥有共享锁(S),导致事务事务(trx_id=1261156958)处于等待状态。
我们知道共享锁(S)跟排他锁(X)是的兼容关系如下图所示,那么为什么会话1(线程ID=52)在表tableB的的bill_id='1'持有共享锁呢?其实如果你修改一下实验条件,你会发现delete任意记录都会被阻塞(例如delete from tableB where bill_id='4';),网友的问题是为什么这里共享锁锁定了整个tableB表呢?
那么现在在有个问题:共享锁的粒度是什么粒度? 答案是InnoDB存储引擎中,共享锁的粒度是行级别的。如下资料所示:
Shared and Exclusive Locks
InnoDB implements standard row-level locking where there are two types of locks, shared (S) locks and exclusive (X) locks.
· A shared (S) lock permits the transaction that holds the lock to read a row.
· An exclusive (X) lock permits the transaction that holds the lock to update or delete a row.
If transaction T1 holds a shared (S) lock on row r, then requests from some distinct transaction T2 for a lock on row r are handled as follows:
· A request by T2 for an S lock can be granted immediately. As a result, both T1 and T2 hold an S lock on r.
· A request by T2 for an X lock cannot be granted immediately.
If a transaction T1 holds an exclusive (X) lock on row r, a request from some distinct transaction T2 for a lock of either type on r cannot be granted immediately. Instead, transaction T2 has to wait for transaction T1 to release its lock on row r.
那么也就是说会话1的UPDATE语句对表tableB中的所有行加了共享锁,为什么会这样呢? 其实共享锁(S)锁一般是锁定读取的行。那么会话1中的SQL执行计划,肯定读取了tableB中所有的行,我们观察执行计划发现,优化器通过对索引idx_bill_id扫描,读取了此表的6条记录。这个也是业务逻辑使然。
mysql> explain
-> UPDATE tableA a
-> LEFT JOIN
-> (SELECT
-> bill_id,MAX(update_time)
-> FROM
-> tableB
-> GROUP BY bill_id) b ON a.id = b.bill_id
-> SET
-> a.name = 'abcd'
-> WHERE
-> a.id = '2';
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+
| 1 | PRIMARY | a | const | PRIMARY | PRIMARY | 12 | const | 1 | NULL |
| 1 | PRIMARY | <derived2> | ref | <auto_key0> | <auto_key0> | 13 | const | 0 | Using where |
| 2 | DERIVED | tableB | index | idx_bill_id | idx_bill_id | 13 | NULL | 6 | NULL |
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+
3 rows in set (0.00 sec)
如果在MySQL 8下面(MySQL 8.0.18下的实验结果跟MySQL 5.6.25下是一致的),格式化对应的执行计划,你会有更形象、直观的认识。
下面我们再改变一下实验中的SQL语句,修改业务逻辑,对比看看一下实验效果。
会话1:
UPDATE tableA a
LEFT JOIN
(SELECT
bill_id,MAX(update_time)
FROM
tableB
WHERE bill_id <='2'
GROUP BY bill_id) b ON a.id = b.bill_id
SET
a.name = 'abcd'
WHERE
a.id = '2';
会话2:
delete from tableB where bill_id='4';
照理来说,会话1中的SQL,在表tableB上,应该走索引区间扫描(rang),不会对bill_id=4的记录加上共享锁(S), 会话2不应该被会话1阻塞。然而实际情况:在MySQL 5.6.25中,我们实验测试发现会话1还是会阻塞会话2,因为会话1的执行计划还是走索引扫描,对表tableB中的6行记录加了共享锁,如下截图所示,即使更新统计信息也好,重建索引也罢,MySQL优化器始终走索引扫描。不清楚为什么会这样。
但是在MySQL 8.0.18中,就会发现会话1不会阻塞会话2,从执行计划来看,在tableB上对索引idx_bill_id进行索引范围扫描,读取记录有4行(bill_id<=2)。也就是说这4行上加上了共享锁。
mysql> explain
-> UPDATE tableA a
-> LEFT JOIN
-> (SELECT
-> bill_id,MAX(update_time)
-> FROM
-> tableB
-> WHERE bill_id <='2'
-> GROUP BY bill_id) b ON a.id = b.bill_id
-> SET
-> a.name = 'abcd'
-> WHERE
-> a.id = '2';
+----+-------------+------------+------------+-------+---------------+-------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+-------------+---------+-------+------+----------+-------------+
| 1 | UPDATE | a | NULL | const | PRIMARY | PRIMARY | 12 | const | 1 | 100.00 | NULL |
| 1 | PRIMARY | <derived2> | NULL | ref | <auto_key0> | <auto_key0> | 13 | const | 1 | 100.00 | NULL |
| 2 | DERIVED | tableB | NULL | range | idx_bill_id | idx_bill_id | 13 | NULL | 4 | 100.00 | Using where |
+----+-------------+------------+------------+-------+---------------+-------------+---------+-------+------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)
mysql> explain format=tree
-> UPDATE tableA a
-> LEFT JOIN
-> (SELECT
-> bill_id,MAX(update_time)
-> FROM
-> tableB
-> WHERE bill_id <='2'
-> GROUP BY bill_id) b ON a.id = b.bill_id
-> SET
-> a.name = 'abcd'
-> WHERE
-> a.id = '2';
+------------------------------------------------------------------------------------------------+
| EXPLAIN |
+------------------------------------------------------------------------------------------------+
| -> Update a
-> Nested loop left join
-> Rows fetched before execution
-> Index lookup on b using <auto_key0> (bill_id='2')
-> Materialize
-> Group aggregate: max(tableB.update_time)
-> Filter: (tableB.bill_id <= '2') (cost=2.06 rows=4)
-> Index range scan on tableB using idx_bill_id (cost=2.06 rows=4)
|
+-----------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql>
其实我们从performance_schema.data_locks中看到,bill_id='3'的记录即使没有被读取,但是也加了共享锁,而bill_id=‘4’的记录因为没有加上共享锁,所以会话2删除这行记录时,申请X锁时,就不会被阻塞。
如果继续上面的实验,将会话2的SQL修改一下
delete from tableB where bill_id='3';
然后我们按照下面的步骤测试实验。
会话1:
mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
| 41 |
+-----------------+
1 row in set (0.00 sec)
mysql> set session autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE tableA a
-> LEFT JOIN
-> (SELECT
-> bill_id,MAX(update_time)
-> FROM
-> tableB
-> WHERE bill_id <='2'
-> GROUP BY bill_id) b ON a.id = b.bill_id
-> SET
-> a.name = 'abcd'
-> WHERE
-> a.id = '2';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
会话2:
mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
| 42 |
+-----------------+
1 row in set (0.00 sec)
mysql> select * from tableB;
+-----+---------+---------------+
| id | bill_id | update_time |
+-----+---------+---------------+
| 100 | 1 | 1586880000000 |
| 200 | 2 | 1586793600000 |
| 300 | 2 | 1586880000000 |
| 400 | 2 | 1586880000000 |
| 500 | 3 | 1586990000000 |
+-----+---------+---------------+
5 rows in set (0.00 sec)
mysql> delete from tableB where bill_id='3';
此时你会发现会话1阻塞了会话2. 那么我来查看一下事务相关的阻塞和锁相关的信息,如下所示:
会话3:
mysql> select thread_id, processlist_id from performance_schema.threads where PROCESSLIST_ID in(41,42);
+-----------+----------------+
| THREAD_ID | PROCESSLIST_ID |
+-----------+----------------+
| 80 | 41 |
| 81 | 42 |
+-----------+----------------+
2 rows in set (0.00 sec)
mysql>
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX\G;
*************************** 1. row ***************************
trx_id: 7979252
trx_state: LOCK WAIT
trx_started: 2020-09-22 10:50:00
trx_requested_lock_id: 139958870846928:33:5:6:139958757162504
trx_wait_started: 2020-09-22 10:50:00
trx_weight: 2
trx_mysql_thread_id: 42
trx_query: delete from tableB where bill_id='3'
trx_operation_state: starting index read
trx_tables_in_use: 1
trx_tables_locked: 1
trx_lock_structs: 2
trx_lock_memory_bytes: 1136
trx_rows_locked: 1
trx_rows_modified: 0
trx_concurrency_tickets: 0
trx_isolation_level: REPEATABLE READ
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 0
trx_is_read_only: 0
trx_autocommit_non_locking: 0
*************************** 2. row ***************************
trx_id: 7979251
trx_state: RUNNING
trx_started: 2020-09-22 10:49:57
trx_requested_lock_id: NULL
trx_wait_started: NULL
trx_weight: 6
trx_mysql_thread_id: 41
trx_query: NULL
trx_operation_state: NULL
trx_tables_in_use: 0
trx_tables_locked: 2
trx_lock_structs: 5
trx_lock_memory_bytes: 1136
trx_rows_locked: 11
trx_rows_modified: 1
trx_concurrency_tickets: 0
trx_isolation_level: REPEATABLE READ
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 0
trx_is_read_only: 0
trx_autocommit_non_locking: 0
2 rows in set (0.00 sec)
ERROR:
No query specified
mysql> SELECT ENGINE
-> ,ENGINE_LOCK_ID
-> ,ENGINE_TRANSACTION_ID
-> ,THREAD_ID
-> ,EVENT_ID
-> ,OBJECT_NAME
-> ,INDEX_NAME
-> ,LOCK_TYPE
-> ,LOCK_MODE
-> ,LOCK_STATUS
-> ,LOCK_DATA
-> FROM performance_schema.data_locks;
+--------+----------------------------------------+-----------------------+-----------+----------+-------------+-------------+-----------+---------------+-------------+------------+
| ENGINE | ENGINE_LOCK_ID | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |
+--------+----------------------------------------+-----------------------+-----------+----------+-------------+-------------+-----------+---------------+-------------+------------+
| INNODB | 139958870846928:1090:139958757165432 | 7979252 | 81 | 34 | tableB | NULL | TABLE | IX | GRANTED | NULL |
| INNODB | 139958870846928:33:5:6:139958757162504 | 7979252 | 81 | 34 | tableB | idx_bill_id | RECORD | X | WAITING | '3', '500' |
| INNODB | 139958870846056:1088:139958757159480 | 7979251 | 80 | 42 | tableA | NULL | TABLE | IX | GRANTED | NULL |
| INNODB | 139958870846056:31:4:9:139958757156440 | 7979251 | 80 | 42 | tableA | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | '2' |
| INNODB | 139958870846056:1090:139958757159568 | 7979251 | 80 | 42 | tableB | NULL | TABLE | IS | GRANTED | NULL |
| INNODB | 139958870846056:33:5:2:139958757156784 | 7979251 | 80 | 42 | tableB | idx_bill_id | RECORD | S | GRANTED | '1', '100' |
| INNODB | 139958870846056:33:5:3:139958757156784 | 7979251 | 80 | 42 | tableB | idx_bill_id | RECORD | S | GRANTED | '2', '200' |
| INNODB | 139958870846056:33:5:4:139958757156784 | 7979251 | 80 | 42 | tableB | idx_bill_id | RECORD | S | GRANTED | '2', '300' |
| INNODB | 139958870846056:33:5:5:139958757156784 | 7979251 | 80 | 42 | tableB | idx_bill_id | RECORD | S | GRANTED | '2', '400' |
| INNODB | 139958870846056:33:5:6:139958757156784 | 7979251 | 80 | 42 | tableB | idx_bill_id | RECORD | S | GRANTED | '3', '500' |
| INNODB | 139958870846056:33:4:2:139958757157128 | 7979251 | 80 | 42 | tableB | PRIMARY | RECORD | S,REC_NOT_GAP | GRANTED | '100' |
| INNODB | 139958870846056:33:4:3:139958757157128 | 7979251 | 80 | 42 | tableB | PRIMARY | RECORD | S,REC_NOT_GAP | GRANTED | '200' |
| INNODB | 139958870846056:33:4:4:139958757157128 | 7979251 | 80 | 42 | tableB | PRIMARY | RECORD | S,REC_NOT_GAP | GRANTED | '300' |
| INNODB | 139958870846056:33:4:5:139958757157128 | 7979251 | 80 | 42 | tableB | PRIMARY | RECORD | S,REC_NOT_GAP | GRANTED | '400' |
| INNODB | 139958870846056:33:4:6:139958757157128 | 7979251 | 80 | 42 | tableB | PRIMARY | RECORD | S,REC_NOT_GAP | GRANTED | '500' |
+--------+----------------------------------------+-----------------------+-----------+----------+-------------+-------------+-----------+---------------+-------------+------------+
15 rows in set (0.00 sec)
mysql> SELECT * FROM performance_schema.data_lock_waits\G;
*************************** 1. row ***************************
ENGINE: INNODB
REQUESTING_ENGINE_LOCK_ID: 139958870846928:33:5:6:139958757162504
REQUESTING_ENGINE_TRANSACTION_ID: 7979252
REQUESTING_THREAD_ID: 81
REQUESTING_EVENT_ID: 34
REQUESTING_OBJECT_INSTANCE_BEGIN: 139958757162504
BLOCKING_ENGINE_LOCK_ID: 139958870846056:33:5:6:139958757156784
BLOCKING_ENGINE_TRANSACTION_ID: 7979251
BLOCKING_THREAD_ID: 80
BLOCKING_EVENT_ID: 42
BLOCKING_OBJECT_INSTANCE_BEGIN: 139958757156784
1 row in set (0.00 sec)
ERROR:
No query specifie
那么为什么在表tableB的id=500或bill_id='3'的记录上有共享锁呢? 我们来看看会话1中SQL的执行计划,执行计划会通过表tableB的索引idx_bill_id的区间索引扫描,读取了4行记录,对这4行记录加上共享锁。那么为什么id=500这条记录上也加上了共享锁呢?
mysql> explain format=tree
-> UPDATE tableA a
-> LEFT JOIN
-> (SELECT
-> bill_id,MAX(update_time)
-> FROM
-> tableB
-> WHERE bill_id <='2'
-> GROUP BY bill_id) b ON a.id = b.bill_id
-> SET
-> a.name = 'abcd'
-> WHERE
-> a.id = '2';
+--------------------------------------------------------------------------------------------------+
| EXPLAIN |
+--------------------------------------------------------------------------------------------------+
| -> Update a
-> Nested loop left join
-> Rows fetched before execution
-> Index lookup on b using <auto_key0> (bill_id='2')
-> Materialize
-> Group aggregate: max(tableB.update_time)
-> Filter: (tableB.bill_id <= '2') (cost=2.06 rows=4)
-> Index range scan on tableB using idx_bill_id (cost=2.06 rows=4)
|
+---------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
说到这里,就必须先简单介绍一下Next-Key Lock,它是结合了Gap Lock和Record Lock的一种锁定算法,在Next-Key Lock算法下,因为InnoDB对于行的查询都是采用了Next-Key Lock的算法,锁定的不是单个值,而是一个范围(GAP)。上面索引值有1,2,3,其记录的GAP的区间如下:是一个左开右闭的空间:(-∞,1],(1,2],(2,3],(3,+∞),该SQL语句锁定的的记录为bill_id <= '2'的行记录,它还会对辅助索引下一个键值(bill_id='3')加上Gap Lock,以及在在记录bill_id='3'上加上共享锁。所以在删除bill_id='3'的记录时,就会遇到阻塞了。
Next-Key Locks
A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.
InnoDB performs row-level locking in such a way that when it searches or scans a table index, it sets shared or exclusive locks on the index records it encounters. Thus, the row-level locks are actually index-record locks. A next-key lock on an index record also affects the “gap” before that index record. That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record. If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.
Suppose that an index contains the values 10, 11, 13, and 20. The possible next-key locks for this index cover the following intervals, where a round bracket denotes exclusion of the interval endpoint and a square bracket denotes inclusion of the endpoint:
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
For the last interval, the next-key lock locks the gap above the largest value in the index and the “supremum” pseudo-record having a value higher than any value actually in the index. The supremum is not a real index record, so, in effect, this next-key lock locks only the gap following the largest index value.
By default, InnoDB operates in REPEATABLE READ transaction isolation level. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows (see Section 15.7.4, “Phantom Rows”).
思考部分
从这个UPDATE语句中,我们可以看到其子查询内tableB所有的行都会加上共享锁。那么要如何优化这个SQL呢? 下面是一种方案,借助临时表,可以避免tableB上的所有记录加上共享锁,影响并发性。
CREATE TEMPORARY TABLE tmp_result
SELECT
bill_id,MAX(update_time)
FROM
tableB
GROUP BY bill_id;
UPDATE tableA a
LEFT JOIN
tmp_result b ON a.id = b.bill_id
SET
a.name = 'abcd'
WHERE
a.id = '2';
另外,我们还要特别留意UPDATE语句中使用子查询的情况的,例如下面这种情况(下面是博客Avoid Shared Locks from Subqueries When Possible中例子)
update ibreg set k=1 where id in (select id from ibcmp where id > 90000);
这样的SQL会导致子查询中的表,例如ibcmp,大范围的加上共享锁,导致DML操作被阻塞,严重的时候,可能产生大量的阻塞。所以可以通过下面方式优化:
方法1:
begin
select group_concat(id) into @ids from ibcmp where id > 90000;
update ibreg set k=1 where id in (@ids);
commit;
方法2:
begin;
select id into outfile '/tmp/id.csv' from ibcmp where id > 90000;
create temporary table t (id int unsigned not null) engine=innodb;
load data infile '/tmp/id.csv' into table t;
update ibreg inner join t on ibreg.id = t.id;
commit;
参考资料:
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
https://www.percona.com/blog/2017/09/25/avoid-shared-locks-from-subqueries-when-possible/
原因是这样的,共享锁(S)锁一般是锁定读取的行,但是你这个测试案例里面,为什么出现了阻塞呢?其实还是因为这样写SQL,导致执行计划里面通过索引读取了tableB中所有的行,所以导致会话2被阻塞。你可以看执行计划(MySQL 8也是这种情况),
MySQL的共享锁阻塞会话案例浅析输入日志标题的更多相关文章
- HTML6注册表单输入日志标题
一.找到元素. var d = document.getElementById("") var d = document.getElementsByName("" ...
- Image Transformation in WPF输入日志标题
Image transformation is a process of rotating and scaling images. Rotating Images There are two ways ...
- 扒一扒各大电商网站的m站都用的什么前端技术输入日志标题
凡客首页使用Swiper和zepto,没有使用jquery , 静态首页+js交互, 资源加载使用 lazyLoad X-AspNet-Version: 4.0.30319 X-AspNetMvc- ...
- HTML4如何让一个DIV居中对齐?float输入日志标题
float:left,right clear:both 如何让一个DIV居中对齐? 第一步:设置外层的DIV的text-align:center; 第二步:设置里层的DIV的margin:auto 以 ...
- Oracle 安装报错 [INS-06101] IP address of localhost could not be determined 解决方法输入日志标题
安装Oracle 11gR2,报错:[INS-06101] IP address of localhost could not be determined 出现这种错误是因为主机名和/etc/host ...
- MySQL锁之三:MySQL的共享锁与排它锁编码演示
一.行锁之MySQL 使用SELECT ... FOR UPDATE 做事务写入前的确认 以MySQL 的InnoDB 为例,预设的Tansaction isolation level 为REPEA ...
- Mysql的共享锁和排他锁(转载)
mysql锁机制分为表级锁和行级锁,本文就和大家分享一下我对mysql中行级锁中的共享锁与排他锁进行分享交流. 共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能 ...
- MySQL复制slave服务器死锁案例
原文:MySQL复制slave服务器死锁案例 MySQL复制刚刚触发了一个bug,该bug的触发条件是slave上Xtrabackup备份的时候执行flushs tables with read lo ...
- MySQL的共享锁与排它锁编码演示
一.行锁之MySQL 使用SELECT ... FOR UPDATE 做事务写入前的确认 以MySQL 的InnoDB 为例,预设的Tansaction isolation level 为REPEA ...
随机推荐
- JavaScript学习系列博客_22_JavaScript this(上下文对象)
this(上下文对象) - 我们每次调用函数时,解析器都会将一个上下文对象作为隐含的参数传递进函数. 使用this来引用上下文对象,根据函数的调用形式不同,this代表的对象也不同.不同的情况: 1. ...
- 痞子衡嵌入式:解锁i.MXRTxxx上FlexSPI模块自带的地址重映射(Remap)功能
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT三位数系列隐藏的FlexSPI Remap功能. 前段时间痞子衡写了一篇文章 <利用i.MXRT1060,1010上新 ...
- html+css知识点以及常见的坑
float 与 绝对定位 共同点:都脱离文档流,不占用原来的位置,后面的内容占有位置 不同点: float后者居上,补齐,且只能在父级盒子内活动,且不能浮动在照片上. 绝对定位脱离文档流后,直接漂浮到 ...
- Solon详解(五)- Solon扩展机制之Solon Plugin
Solon 中也有一种非常解耦的扩展机制:Solon Plugin.这种扩展机制和Spring Factories很像,和SPI也很像. 一.Solon 中的扩展机制 在Solon的扩展插件加载机制, ...
- git提交限制后提交出错的暴力解决 (使用小乌龟)
1.右键-> TortoiseGit-> 显示日志 2.右键->重置到哪个版本 3. 重新修改提交信息提交
- Mysql 如何实现全文检索,关键词跑分
一.前言 今天一个同事问我,如何使用 Mysql 实现类似于 ElasticSearch 的全文检索功能,并且对检索关键词跑分?我当时脑子里立马产生了疑问?为啥不直接用es呢?简单好用还贼快.但是听他 ...
- 深入了解Redis(3)-对象
Redis主要的数据结构有简单动态字符串(SDS).双端链表.字典.压缩列表.整数集合,等等.但Redis并没有直接使用这些数据结构来实现键值对数据库, 而是基于这些数据结构创建了一个对象系统, 这个 ...
- secureCRT中sftp的使用
securecrt 按下ALT+P就开启新的会话 进行ftp操作.输入:help命令,显示该FTP提供所有的命令pwd: 查询linux主机所在目录(也就是远程主机目录)lpwd: 查询本地目录(一般 ...
- Python 带你一键生成朋友圈超火的九宫格短视频
1. 场景 如果你经常刷抖音和微信朋友圈,一定发现了最近九宫格短视频很火! 从朋友圈九宫格图片,到九宫格视频,相比传统的图片视频,前者似乎更有个性和逼格 除了传统的剪辑软件可以实现,是否有其他更加快 ...
- Eazfuscator.net 2020 虚拟化保护(Virtulization)机制分析
一.前言与目标 周末接触了一款游戏They are billons即亿万僵尸,想添加一些新的玩法元素比如新的兵种进去, 打开dnspy看了下,发现是Eazfuscator.net的Virtulizat ...