diff --git a/mysql/mysql文档/锁.md b/mysql/mysql文档/锁.md index 12d1d87..4df257f 100644 --- a/mysql/mysql文档/锁.md +++ b/mysql/mysql文档/锁.md @@ -163,4 +163,118 @@ Next-Key Lock是为了解决幻读问题而引入的,如果事务T1已经锁 但是,`如果查询的索引为unique索引,那么innodb则是会针对next-key lock进行优化,将其降级为record lock,紧锁住索引本身,而不对范围进行加锁。` +#### insert-intention lock +插入意向锁是在插入行数据前,由`插入操作`设置的间隙锁。多个事务在针对同一间隙进行插入操作时,`如果他们并不在同一位置进行插入,那么各个事务之间并不需要彼此等待`。 + +例如,多个事务需要在`(4, 7)`的间隙之间插入值,但是A事务插入5,B事务插入6,那么在获取待插入行的X锁之前,都需要通过insert intention lock来锁住`(4, 7)`的间隙。插入意向锁之间并不会相互阻塞。 + + +### 加锁示例 +首先,创建数据库并预制数据 +```sql +-- ddl +CREATE TABLE `t_learn_lock` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `content` varchar(32) NOT NULL DEFAULT '', + `lv` int NOT NULL DEFAULT '-1', + PRIMARY KEY (`id`), + KEY `idx_lv` (`lv`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci + +-- dml +insert into t_lear_lock(id, content, lv) values + (1, 'a', 3), + (2, 'd', 7), + (3, 'f', 9), + (4, 'o', 13); +``` + +会话的事务默认隔离级别为repeatable read + +#### performance_schema.data_locks +`performace_schema.data_locks`中存储了加锁情况,其表结构如下: +- engine:该锁代表的存储引擎 +- engine_transaction_id: 存储引擎中标识事务的id +- thread_id: `创建该锁的会话`的线程id +- object_schema: 包含该表的schema +- object_name:表名称 +- index_name: 被加锁的索引名称 +- lock_type: 加锁的类型,对于innodb,其值为record或table,代表行锁或表锁 +- lock_mode: 请求锁的方式,对于innodb,其格式为`S[,GAP], X[,GAP], IS[,GAP], IX[,GAP], AUTO_INC, and UNKNOWN`。 +- lock_data:锁相关的数据 +- lock_status: 请求锁的状态,在innodb中,可以为`GRANTED或WAITING` + +> #### lock mode +> 在lock mode字段中,除了auto_inc和unknown之外的值都代表gap lock +> +> 其取值如下: +> - X: next_key_locking,`代表x和x之前的间隙` +> - X,GAP:间隙锁,`代表x之前的间隙,不包含x记录本身` +> - X, REC_NOT_GAP:`代表X记录本身,不包含之前的间隙` +> - X,GAP,INSERT_INTENTION:插入意向锁,彼此之前不互斥 + +#### select ... 加锁情况 +在开启事务后,执行`select * from t_lear_lock`语句,默认`并不会为index record或表加上任何锁`,在可重复读的隔离级别下,默认会采用无锁的一致性读方式,来读取数据的历史版本快照,期间并不需要进行任何加锁操作。 + +#### select ... for update 加锁情况 +在开启事务后,如果执行`select ... for update`语句,那么分为如下几种场景: + +##### select语句未命中索引 +若执行`select * from t_learn_lock where content < 'h' and content > 'e' for update for update;`语句,由于content字段上并没有添加索引,故而该查询语句并不会命中索引,possible_keys为空。 + +在未命中索引的情况下,`select ... for update`语句`会针对主键索引来进行加锁`,其加锁情况如下: + +|INDEX_NAME|LOCK_TYPE|LOCK_MODE|LOCK_STATUS|LOCK_DATA| +|----------|---------|---------|-----------|---------| +||TABLE|IX|GRANTED|| +|PRIMARY|RECORD|X|GRANTED|supremum pseudo-record| +|PRIMARY|RECORD|X|GRANTED|1| +|PRIMARY|RECORD|X|GRANTED|2| +|PRIMARY|RECORD|X|GRANTED|3| +|PRIMARY|RECORD|X|GRANTED|4| + +其中,`select * from t_learn_lock where content < 'h' and content > 'e' for update for update;`会查询出id为`3`的记录,但是由于未命中索引,`该语句会针对主键中所有的index record进行加锁`(包括 supremum prseudo-record)。 + +> ##### supremum preseudo-record +> 该index record并不是一条真实存在的索引记录,其代表了`比索引中所有记录的值都大的一条虚拟记录`,类似有序链表中的尾节点。 + +##### select语句命中非unique索引 +当执行的select语句,其where条件命中非unique索引,`那么,其会针对命中索引以及主键索引都进行加锁`。 + +例如,`select * from t_learn_lock where lv between 4 and 8 for update;`,该语句会针对`idx_lv`索引记录和主键索引记录进行加锁,加锁状况如下: + +|INDEX_NAME|LOCK_TYPE|LOCK_MODE|LOCK_STATUS|LOCK_DATA| +|----------|---------|---------|-----------|---------| +||TABLE|IX|GRANTED|| +|idx_lv|RECORD|X|GRANTED|7, 2| +|idx_lv|RECORD|X|GRANTED|9, 3| +|PRIMARY|RECORD|X,REC_NOT_GAP|GRANTED|2| + +上述查询语句会命中`(2, 'd', 7)`这条记录,但是,其针对索引加锁的操作如下: +- 对于`idx_lv`索引记录,不仅对语句查询出的`(7,2)`这条索引记录进行了加锁,还针对了下一条索引记录`(9,3)`进行了加锁 +- 对于`primary`主键索引记录,则只针对查询出的id为2的记录进行了加锁`(2)` + +对于`idx_lv`中`(9,3)`这条记录进行加锁,主要是为了防止幻读,当对`(9, 3)`这条记录加锁后,如果后续其他事务想要向lv值为`(7,9)`的范围内进行加锁时,需要获取 + + + + +#### update ... 加锁情况 +##### update语句未命中索引的加锁情况 +类似于`select ... for update`未命中索引的情况,update操作在未命中索引的情况下,`也会针对主键索引的记录进行加锁,并且会获取主键索引中所有记录的锁`。 + +其加锁情况和`select ... for update`一样,例如`update t_learn_lock set content = 'fuck' where content = 'm';`,该语句并不会实际修改任何行记录,但是还是会锁住主键中所有的index record。 + +|INDEX_NAME|LOCK_TYPE|LOCK_MODE|LOCK_STATUS|LOCK_DATA| +|----------|---------|---------|-----------|---------| +||TABLE|IX|GRANTED|| +|PRIMARY|RECORD|X|GRANTED|supremum pseudo-record| +|PRIMARY|RECORD|X|GRANTED|1| +|PRIMARY|RECORD|X|GRANTED|2| +|PRIMARY|RECORD|X|GRANTED|3| +|PRIMARY|RECORD|X|GRANTED|4| + + + +