innodb 锁分裂继承与迁移
innodb行锁简介
- 行锁类型
LOCK_S:共享锁
LOCK_X: 排他锁
- GAP类型
LOCK_GAP:只锁间隙
LOCK_REC_NO_GAP:只锁记录
LOCK_ORDINARY: 锁记录和记录之前的间隙
LOCK_INSERT_INTENTION: 插入意向锁,用于insert时检查锁冲突
每个行锁由锁类型和GAP类型组成
例如:
LOCK_X|LOCK_ORDINARY 表示对记录和记录之前的间隙加排他锁
LOCK_S|LOCK_GAP 表示只对记录前的间隙加共享锁
锁的兼容性:
值得注意的是,持有GAP的锁(LOCK_GAP和LOCK_ORDINARY)与其他非LOCK_INSERT_INTENTION的锁都是兼容的,也就是说,GAP锁就是为了防止插入的。
详细可以参考之前的月报
innodb 锁分裂、继承与迁移
这里的锁分裂和合并,只是针对innodb行锁而言的,而且一般只作用于GAP类型的锁。
锁分裂
插入的记录的间隙存在GAP锁,此时此GAP需分裂为两个GAP
lock_rec_inherit_to_gap_if_gap_lock:
for (lock = lock_rec_get_first(block, heap_no);
lock != NULL;
lock = lock_rec_get_next(heap_no, lock)) {
if (!lock_rec_get_insert_intention(lock)
&& (heap_no == PAGE_HEAP_NO_SUPREMUM
|| !lock_rec_get_rec_not_gap(lock))) {
lock_rec_add_to_queue(
LOCK_REC | LOCK_GAP | lock_get_mode(lock),
block, heir_heap_no, lock->index,
lock->trx, FALSE);
}
}
锁继承
删除的记录前存在GAP锁,此GAP锁会继承到要删除记录的下一条记录上
lock_rec_inherit_to_gap:
for (lock = lock_rec_get_first(block, heap_no);
lock != NULL;
lock = lock_rec_get_next(heap_no, lock)) {
if (!lock_rec_get_insert_intention(lock)
&& !((srv_locks_unsafe_for_binlog
|| lock->trx->isolation_level
<= TRX_ISO_READ_COMMITTED)
&& lock_get_mode(lock) ==
(lock->trx->duplicates ? LOCK_S : LOCK_X))) {
lock_rec_add_to_queue(
LOCK_REC | LOCK_GAP | lock_get_mode(lock),
heir_block, heir_heap_no, lock->index,
lock->trx, FALSE);
}
}
锁迁移
B数结构变化,锁信息也会随之迁移. 锁迁移过程中也涉及锁继承。
锁分裂示例
- 锁分裂例子
set global tx_isolation='repeatable-read';
create table t1(c1 int primary key, c2 int unique) engine=innodb;
insert into t1 values(1,1);
begin;
# supremum 记录上加 LOCK_X|LOCK_GAP 锁住(1~)
select * from t1 where c2=2 for update;
# 发现插入(3,3)的间隙存在GAP锁,因此给(3,3)加LOCK_X|LOCK_GAP锁。这样依然锁住了(1~)
insert into t1 values(3,3);
这里如果插入(3,3)没有给(3,3)加LOCK_X|LOCK_GAP,那么其他连接插入(2,2)就可以成功
锁继承示例
- 隔离级别repeatable-read
===== RR =====
set global tx_isolation='repeatable-read';
create table t1(c1 int primary key, c2 int unique) engine=innodb;
insert into t1 values(1,1),(2,2);
#会话信息
session 1: | session 2:
begin; |
#(1,1) 加LOCK_X|LOCK_REC_NOT_GAP |
delete from t1 where c1=1; |
|
| begin;
| # (1,1)加LOCK_X|LOCK_ORDINARY 等待
| select * from t1 where c1 <= 1 for update;
commit; |
| #(1,1)被删除,purge清理delete mark时,(1,1)上的锁继承到(2,2)上,锁为LOCK_X|LOCK_GAP
| #同时(1,1)上的锁都释放,session 2等待成功
验证:session 1执行insert into t1 values(1,1)发生了锁等待,说明(2,2)上有gap锁
mysql> select * from information_schema.innodb_locks;
+------------------------+-------------+-----------+-----------+-----------------+------------+------------+-----------+----------+-----------+
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+------------------------+-------------+-----------+-----------+-----------------+------------+------------+-----------+----------+-----------+
| 16582717714:888654:4:3 | 16582717714 | X,GAP | RECORD | `cleaneye`.`t1` | c2 | 888654 | 4 | 3 | 2 |
| 16582692183:888654:4:3 | 16582692183 | X,GAP | RECORD | `cleaneye`.`t1` | c2 | 888654 | 4 | 3 | 2 |
+------------------------+-------------+-----------+-----------+-----------------+------------+------------+-----------+----------+-----------+
2 rows in set (0.01 sec)
其中session 2 在(2,2) 加了LOCK_X|LOCK_GAP
session 1 在(2,2) 加了LOCK_X|LOCK_GAP|LOCK_INSERT_INTENTION. LOCK_INSERT_INTENTION与LOCK_GAP冲突发生等待
- 隔离级别read-committed
===== RC =====
set global tx_isolation='read-committed';
drop table t1;
create table t1(c1 int primary key) engine=innodb;
insert into t1 values(1),(2);
#会话信息
session 1 | session 2
begin; |
#(1) 加LOCK_X|LOCK_REC_NOT_GAP |
delete from t1 where c1=1; |
|
| begin;
| #(1)加LOCK_S|LOCK_REC_NOT_GAP 等待
| select *from t1 where c1 <=1 lock in share mode;
|
COMMIT: |
| #(1)被删除,purge清理delete mark时,(1)上的锁继承到(2)上,锁为LOCK_S|LOCK_GAP
| # 同时(1)上的锁都释放,session 2等待成功
|
验证
session 1执行insert into t1 values(1)发生了锁等待,说明(2)上有gap锁
mysql> select * from information_schema.innodb_locks;
+------------------------+-----------------+-----------+-----------+-------------+------------+------------+-----------+----------+-----------+
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+------------------------+-----------------+-----------+-----------+-------------+------------+------------+-----------+----------+-----------+
| 1705:32:3:3 | 1705 | X,GAP | RECORD | `test`.`t1` | PRIMARY | 32 | 3 | 3 | 2 |
| 421590768578232:32:3:3 | 421590768578232 | S,GAP | RECORD | `test`.`t1` | PRIMARY | 32 | 3 | 3 | 2 |
+------------------------+-----------------+-----------+-----------+-------------+------------+------------+-----------+----------+-----------+
X.GAP insert 加锁LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION
S.GAP 加锁LOCK_S|LOCK_GAP,记录(2)从删除的记录(1)继承过来的GAP锁
而实际在读提交隔离级别上,insert into t1 values(1)应该可以插入成功,不需要等待的,这个锁是否继承值得商榷。
来看一个插入成功的例子
===== RC =====
set global tx_isolation='read-committed';
drop table t1;
create table t1(c1 int primary key) engine=innodb;
insert into t1 values(1),(2);
# 会话信息
session 1 | session 2
|
| begin;
| #(1)加LOCK_S|LOCK_REC_NOT_GAP
| # 查询结果为(1,2)
| select *from t1 where c1 <=1 lock in share mode;
|
begin; |
# 检查(1)上的锁与LOCK_X|LOCK_GAP|LOCK_INSERT_INTENTION |
# 不冲突,插入成功 |
insert into t1 values(0); |
| #再次查询结果为(0,1,2)
commit; | select *from t1 where c1 <=3 lock in share mode;
- 隔离级别serializable
===== SERIALIZABLE =====
set global tx_isolation='SERIALIZABLE';
drop table t1;
create table t1(c1 int primary key) engine=innodb;
insert into t1 values(1),(2);
# 会话信息
session 1: | session 2:
begin; |
#(1) 加LOCK_X|LOCK_REC_NOT_GAP |
delete from t1 where c1=1; |
|
| begin;
| #(1)上加LOCK_S|LOCK_ORDINARY 等待
| select *from t1 where c1 <=1 ;
|
commit; |
|
| #(1)被删除,purge清理delete mark时,(1)上的锁继承到(2)上,锁为LOCK_S|LOCK_GAP
| # 同时(1)上的锁都释放,session 2等待成功
|
验证方法同read-committed。
B树结构变化与锁迁移
B树节点发生分裂,合并,删除都会引发锁的变化。锁迁移的原则是,B数结构变化前后,锁住的范围保证不变。
我们通过例子来说明
节点分裂
假设原节点A(infimum,1,3,supremum) 向右分裂为B(infimum,1,supremum), C(infimum,3,supremum)两个节点
infimum为节点中虚拟的最小记录,supremum为节点中虚拟的最大记录
假设原节点A上锁为3上LOCK_S|LOCK_ORIDNARY,supremum为LOCK_S|LOCK_GAP,实际锁住了(1~)
锁迁移过程大致为:1)将3上的gap锁迁移到C节点3上
2)将A上supremum迁移继承到C的supremum上
3)将C上最小记录3的锁迁移继承到B的supremum上
迁移完成后锁的情况如下(lock_update_split_right)
B节点:suprmum LOCK_S|LOCK_GAP
C节点:3 LOCK_S|LOCK_ORINARY, suprmum LOCK_S|GAP迁移后仍然锁住了范围(1~)
节点向左分裂情形类似
节点合并
以上述节点分裂的逆操作来讲述合并过程
B(infimum,1,supremum), C(infimum,3,supremum)两个节点,向左合并为A节点(infimum,1,3,supremum)
其中B,C节点锁情况如下
B节点:suprmum LOCK_S|LOCK_GAP
C节点:3 LOCK_S|LOCK_ORINARY, suprmum LOCK_S|GAP迁移流程如下(lock_update_merge_left):
1)将C节点锁记录3迁移到B节点
2)将B节点supremum迁移继承到A的supremum上
迁移后仍然锁住了范围(1~)
节点向右合并情形类似
节点删除
如果删除节点存在左节点,则将删除节点符合条件的锁,迁移继承到左节点supremum上
否则将删除节点符合条件的锁,迁移继承到右节点最小用户记录上
参考lock_update_discard
锁继承相关的BUG
bug#73170 二级唯一索引失效。这个bug触发条件是删除的记录没有被purge, 锁还没有被继承的。如果锁继承了就不会出现问题。
bug#76927 同样是二级唯一索引失效。这个bug是锁继承机制出了问题。
以上两个bug详情参考这里
innodb 锁分裂继承与迁移的更多相关文章
- MySQL · 特性分析 · innodb 锁分裂继承与迁移
http://mysql.taobao.org/monthly/2016/06/01/ innodb行锁简介 行锁类型 LOCK_S:共享锁 LOCK_X: 排他锁 GAP类型 LOCK_GAP:只锁 ...
- MySQL数据恢复和复制对InnoDB锁机制的影响
MySQL通过BINLOG记录执行成功的INSERT,UPDATE,DELETE等DML语句.并由此实现数据库的恢复(point-in-time)和复制(其原理与恢复类似,通过复制和执行二进制日志使一 ...
- 由innodb锁引起的数据库相关
innodb 锁的问题 1.事务 原子性:要么成功,要么失败 一致性:前后数据保持一致状态 隔离性:多个事务并行,相互不影响 持久性:事务提交之后,对数据的影响是永久性的,即使故障也可以保持. 2.并 ...
- InnoDB锁机制分析
InnoDB锁机制常常困扰大家,不同的条件下往往表现出不同的锁竞争,在实际工作中经常要分析各种锁超时.死锁的问题.本文通过不同条件下的实验,利用InnoDB系统给出的各种信息,分析了锁的工作机制.通过 ...
- [转载] 数据库分析手记 —— InnoDB锁机制分析
作者:倪煜 InnoDB锁机制常常困扰大家,不同的条件下往往表现出不同的锁竞争,在实际工作中经常要分析各种锁超时.死锁的问题.本文通过不同条件下的实验,利用InnoDB系统给出的各种信息,分析了锁的工 ...
- mysql innodb锁简析(2)
继续昨天的innodb锁的分析: 注:此博文参考一下地址,那里讲的也很详细.http://xm-king.iteye.com/blog/770721 mysql事务的隔离级别分为四种,隔离级别越高,数 ...
- Innodb 锁系列2 事务锁
上一篇介绍了Innodb的同步机制锁:Innodb锁系列1 这一篇介绍一下Innodb的事务锁,只所以称为事务锁,是因为Innodb为实现事务的ACID特性,而添加的表锁或者行级锁. 这一部分分两篇来 ...
- Innodb 锁 (简单笔记)
看过很多innodb锁的文章,已经明白的就不写了,简单做个笔记 Innodb 锁的兼容性: 1.意向锁和意向锁之间都是兼容的 2.X(排他锁)与任何锁都是不兼容的 3.排他意向锁 IX 于S锁是不 ...
- MySQL- InnoDB锁机制
InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION):二是采用了行级锁.行级锁与表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题.下面我们先介绍一点背景知识 ...
随机推荐
- 日期对象-Date
新建日期对象 var date = new Date(); getTime() 从 1970年 1月 1日开始计算到 Date 对象中的时间之间的毫秒数. getFullYear() ...
- vertx简单客户端创建
import java.util.HashMap;import java.util.Map; import com.yunva.vertx.test.vertproject.util.JsonUtil ...
- 121. Best Time to Buy and Sell Stock (一) leetcode解题笔记
121. Best Time to Buy and Sell Stock Say you have an array for which the ith element is the price of ...
- Trie 最长前缀匹配串的实现
http://blog.csdn.net/hguisu/article/details/8131559
- PhoneGap--001 入门 安装
PhoneGap 百度百科 PhoneGap 中文网 3.0 安装使用 今天也配置好phonegap3.0 android开发环境了,操作系统是win7,就楼主文章做些补充. 我是按phonegap官 ...
- 前端js 判断输入的必须是数字,判断金钱
//输入的必须是数字 $(".xzjl").on("keyup", ".num", function () { var v = $(this ...
- sys.dm_tran_locks,
sys.dm_tran_locks 返回有关当前活动的锁管理器资源的信息.向锁管理器发出的已授予锁或正等待授予锁的每个当前活动请求分别对应一行. 列名 数据类型 说明 resource_type nv ...
- 关于swfupload,客户端中文乱码解决方案!
公司做了个邮箱系统,上传附件用到了swfupload控件,测试成功上线后hr找我说上传附件中文乱码. 奇怪了,就只有她的电脑出问题,我找了好几台电脑,虚拟机也跑了怎么就找不到问题. 后来网上查了好久, ...
- C/C++编译链接过程详解
有些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样的错误信息不能定位到某 ...
- svn+ssh方式svn服务器和客户端的配置[转载]
本文摘自:http://hi.baidu.com/farmerluo/item/e7d9d72d098afc0a42634abb 我们最近一个项目用的那几台服务器都是客户给的,但是管理非常严格,只给我 ...