InnoDB之隐式锁
June 16, 2024
MySQL版本:8.3.0。 隔离级别:RC。
为了节约资源,尽可能避免生成不必要的锁结果,InnoDB设计了隐式锁。隐式锁是指在执行语句时并不生成锁,而在特定场景触发时才生成锁。主要用于插入场景。
下面我们分别就聚簇索引和二级索引上的隐式锁举例说明。
示例表:
CREATE TABLE `t3` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`c` int NOT NULL DEFAULT '0',
`d` int unsigned NOT NULL DEFAULT '0',
`f` int unsigned NOT NULL DEFAULT '0',
`e` int unsigned NOT NULL DEFAULT '0',
`g` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_cdf` (`c`,`d`,`f`),
KEY `idx_d` (`d`)
) ENGINE=InnoDB AUTO_INCREMENT=675002 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
聚簇索引上的隐式锁 #
每条聚簇索引记录中都有一个隐藏列trx_id,记录最后一次修改改记录的事务的事务id。当我们在事务(我们这里称为事务A)中向聚簇索引中新插入一条记录后,其trx_id就是当前事务的事务id,但这时并不会为这条记录生成对应的锁。等到其它事务(事务B)更新这条记录时,判断如果这条事务是活跃事务,则为这条记录生成锁,为事务A持有,事务B进入等待状态。
第一步,事务A中插入一条记录。
插入语句:
mysql> begin;
Query OK, 0 rows affected (0.01 sec)
mysql> insert into t3 (id,c,d,e,f) values(675001,1,1,1,1);
Query OK, 1 row affected (0.00 sec)
在上面的语句中,我们向t3表中插入了一条主键id值为675001的记录。下面我们通过performance_schema库中的data_locks表来查看一下对应的锁情况:
mysql> select engine_transaction_id,thread_id,object_name,index_name,lock_type,lock_mode,lock_status,lock_data from performance_schema.data_locks;
+-----------------------+-----------+-------------+------------+-----------+-----------+-------------+-----------+
| engine_transaction_id | thread_id | object_name | index_name | lock_type | lock_mode | lock_status | lock_data |
+-----------------------+-----------+-------------+------------+-----------+-----------+-------------+-----------+
| 1075542 | 49 | t3 | NULL | TABLE | IX | GRANTED | NULL |
+-----------------------+-----------+-------------+------------+-----------+-----------+-------------+-----------+
1 row in set (0.00 sec)
字段说明:
- engine_transaction_id:事务id。
- thread_id:线程id。
- object_name:表名。
- index_name:索引名。
- lock_type:锁类型。
- lock_mode:锁模式。
- lock_status:锁状态。
- lock_data:锁数据。
可以看到,只有t3上的IX锁,没有对应的记录锁。
第二步,在事务B中更新对应的记录。 我们在另一个事务B中更新这条记录,语句如下:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update t3 set d=11 where id=675001;
执行后,事务B进入等待状态。我们再来看一下现在的锁情况:
mysql> select engine_transaction_id,thread_id,object_name,index_name,lock_type,lock_mode,lock_status,lock_data from performance_schema.data_locks;
+-----------------------+-----------+-------------+------------+-----------+---------------+-------------+-----------+
| engine_transaction_id | thread_id | object_name | index_name | lock_type | lock_mode | lock_status | lock_data |
+-----------------------+-----------+-------------+------------+-----------+---------------+-------------+-----------+
| 1075543 | 50 | t3 | NULL | TABLE | IX | GRANTED | NULL |
| 1075543 | 50 | t3 | PRIMARY | RECORD | X,REC_NOT_GAP | WAITING | 675001 |
| 1075542 | 49 | t3 | NULL | TABLE | IX | GRANTED | NULL |
| 1075542 | 50 | t3 | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 675001 |
+-----------------------+-----------+-------------+------------+-----------+---------------+-------------+-----------+
4 rows in set (0.00 sec)
可以看到,事务A(1075542)持有t3上主键索引记录id为675001这条记录的记录锁,事务B(1075543)请求这条记录的记录锁,进入等待状态。 但是要注意的是,这个锁是事务A持有的,但是却是事务B所在的线程(50)添加的。
二级索引上的隐式锁 #
在二级索引记录钟,没有隐藏列trx_id。但是,二级索引页面中存储有更新当前页面的事务的事务id的最大值,这个属性值是PAGE_MAX_TRX_ID。如果PAGE_MAX_TRX_ID当前最后活跃事务的事务id,则说明这个页面是活跃的,需要为生成锁。
我们下面再来做个测试。
第一步,事务A中插入一条记录。 这一步我们和上面的测试相同,在此不再赘述,但我们只执行之前,将之前的事务都rollback,回归初始状态。
第二步,在事务B中更新对应的记录。 我们在事务B中执行下面的语句:
mysql> begin;
Query OK, 0 rows affected (0.01 sec)
mysql> update t3 set e=11 where d=1 limit 1;
执行之后,事务B进入等待状态。我们查询data_locks表来查看锁状态:
mysql> select engine_transaction_id,thread_id,object_name,index_name,lock_type,lock_mode,lock_status,lock_data from performance_schema.data_locks;
+-----------------------+-----------+-------------+------------+-----------+---------------+-------------+-----------+
| engine_transaction_id | thread_id | object_name | index_name | lock_type | lock_mode | lock_status | lock_data |
+-----------------------+-----------+-------------+------------+-----------+---------------+-------------+-----------+
| 1075545 | 50 | t3 | NULL | TABLE | IX | GRANTED | NULL |
| 1075545 | 50 | t3 | idx_d | RECORD | X,REC_NOT_GAP | WAITING | 1, 675001 |
| 1075544 | 49 | t3 | NULL | TABLE | IX | GRANTED | NULL |
| 1075544 | 50 | t3 | idx_d | RECORD | X,REC_NOT_GAP | GRANTED | 1, 675001 |
+-----------------------+-----------+-------------+------------+-----------+---------------+-------------+-----------+
4 rows in set (0.00 sec)
可以看到,事务A(1075544)持有index_d上的锁,事务B(1075545)在等待。