决战圣地玛丽乔亚Day17 ----意向锁/MDL锁
算法:
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
回溯算法的基本写法:
组合是有序的,1,2 和2,1算作一个结果。
所以如果判断n个数取k个的所有结果集。并返回结果集
首先对于取的顺序方式可以参考
从1开始取,每次在上一次的index基础上往下去取。
如果取够k个进行返回。
还要注意的一点是,取完最终结果需要回溯!这是精髓所在。
1,2取完, 2回溯。
1,3取完,3回溯。
1, 4
然后发现这是的第二层的startindex到头,result集合被赋值完毕, 【1,2】【1,3】【1,.4】
1.递归参数和返回值
这一步更适合整体逻辑理顺后进行编写,如果想清楚参数和返回值,就要想先想清楚大体的逻辑才行。
vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件单一结果
void backtracking(int n, int k, int startIndex)
2.回溯函数的终止条件
if (path.size() == k) {
result.push_back(path);
return;
}
终止条件是取够数量
3.单层的搜索逻辑
for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历
path.push_back(i); // 处理节点
backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
path.pop_back(); // 回溯,撤销处理的节点
}
实际上,这个for循环,里面的backtracking是每一层,直到达到终止条件才是进入到了最深层。
path.pop_back实际上是从最深的一层往外弹出,寻找更多地可能性。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
MYSQL的行锁: 独占锁、共享锁
表锁:除了独占锁共享锁之外,还有意向锁和MDL锁。
意向锁:
意向锁是一种不与行级锁冲突表级锁。
意向锁数据引擎自己维护,自己无法去操作。
在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在在数据表的对应意向锁。
需要解决的问题:
如果事务A先给表加了排他锁,没有提交的情况下,事务B再去加共享锁就需要检查:表是否存在排它锁,表每一行是否存在排它锁。如果真的去检测每一行,效率很差,这时候就可以用意向锁。
意向锁和意向锁之间不冲突。
共享锁和意向共享锁兼容。
其他的意向锁和 共享/排他锁冲突。
意向锁是表级锁,不与行级锁冲突。这里举例的S、X都是表级锁。
意向锁和表级的排他/共享锁的互斥关系如图,意向共享锁和共享锁之间不会互斥
这一段写的很好:
摘自 https://juejin.cn/post/6844903666332368909
总结
- InnoDB 支持
多粒度锁
,特定场景下,行级锁可以与表级锁共存。 - 意向锁之间互不排斥,但除了 IS 与 S 兼容外,
意向锁会与 共享锁 / 排他锁 互斥
。 - IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突。
- 意向锁在保证并发性的前提下,实现了
行锁和表锁共存
且满足事务隔离性
的要求。
刚开始意向锁的出现是为了解决:我一个锁去查一个表是否加了行的独占锁,如果每行去查效率低下的问题。所以在加独占锁的时候给整个表加一个意向独占锁,后面的事务看到就知道已经有独占锁了。
如果有共享锁过来想拿表的共享锁,那么直接看到意向排他锁,被阻塞。如果有事务想拿某一行的排他锁,那么先拿到意向独占锁,然后看想加锁的那一行是否加了独占锁,如果没加就可以获取到那一行的独占锁。
MDL锁:
不需要显示的调用,在我们对表数据进行CRUD时,加MDL读锁;当我们对表进行结构修改的时候,加MDL写锁。为的是保证数据一致性,进行阻塞。但是如果是热点表会大量超时问题。
读读共享,读写互斥,写写互斥。
所以我们再修改表结构的时候,需要等所有的MDL锁释放才能拿到MDL写锁,拿到写锁后,其他线程的CRUD操作被阻塞,等到修改表结构结束才可以释放。
同时普及一下常见的名词:DDL(数据定义语言)CREATE TABLE/VIEW/INDEX/SYN/CLUSTER
DML(数据操纵语言)Insert/update/delete
区间锁:
1.间隙锁:
显示使用间隙锁:
SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;
同样是是select for update语法,但是需要一个区间范围。
首先要了解间隙锁解决的问题是什么:
这是在可重复读RR隔离级别下会出现的幻读现象。
幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。
两次查询之前,被另一事务修改数据,导致读取不一致。
幻读的第二种可能(mvcc修改幻读):
事务A查结果集,set1
事务B修改结果集数据,结果集变为set2
事务A由于MVCC读到的还是 set1
事务A执行update操作后
事务A读到的结果集为set1
所以能看出来,对于读数据mvcc可以解决幻读情况,对于修改数据,mvcc还是会出现幻读。但是其实这种不算幻读
首先要什么是当前读,什么是快照读。
快照读是用mvcc多版本并发控制,解决了幻读现象,读的是之前的历史版本,不加锁。(数据行的版本号要小于或等于当前是事务的系统版本号,这样也就确保了读取到的数据是当前事务开始前已经存在的数据,或者是自身事务改变过的数据)
要注意快照产生在第一次select。
当前读是
update/insert/delete等对数据库操作的sql,更新版本号。 读当前数据,加锁。是会产生幻读的现象,所以在当前读的情况下,如何解决幻读????
所以解决幻读:
1.快照读:mvcc来解决。
2.当前读:
如果范围是主键:对主键索引加recordLock
如果范围不是主键:通过next-key(record-lock+gap lock)处理
1,Record Lock:单个行记录上的锁。
2,Gap Lock:
当我们用范围条件而不是相等条件索引数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项枷锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。
InnoDB也会对这个“间隙”枷锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。为了防止其他事务在间隙内操作造成幻读加的锁。这样做会阻塞数据插入到间隙中
。
3,Next-Key Lock:(行锁+间隙锁),锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。
总结:
记录锁、间隙锁、临键锁,都属于排它锁
隔离级别RR
唯一索引只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙锁,指定给某条存在的记录加锁的时候,只会加记录锁,不会产生间隙锁
普通索引不管是锁住单条,还是多条记录,都会产生间隙锁
间隙锁会封锁该条记录相邻两个键之间的空白区域,防止其它事务在这个区域内插入、修改、删除数据,这是为了防止出现 幻读 现象;
next-lock加锁,范围内阻塞:
-
select * from table where id<6 lock in share mode;--共享锁
-
select * from table where id<6 for update;--排他锁
需要注意的一点,如果是普通的查询是走快照读,只有for update或者 lock in share mode这种才会当前读。
加锁原则:
- 1.加锁的基本单位是(next-key lock),他是前开后闭原则
- 2.插叙过程中访问的对象会增加锁
- 3.索引上的等值查询--给唯一索引加锁的时候,临键锁升级为行锁
- 4.索引上的等值查询--向右遍历时最后一个值不满足查询需求时,next-key lock 退化为间隙锁
- 5.唯一索引上的范围查询会访问到不满足条件的第一个值为止
需要注意的是,当id列上没有索引时,SQL会走聚簇索引的全表扫描进行过滤,由于过滤是在MySQL Server层面进行的。因此每条记录(无论是否满足条件)都会被加上X锁。但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁。但是不满足条件的记录上的加锁/放锁动作是不会省略的。所以在没有索引时,不满足条件的数据行会有加锁又放锁的耗时过程。
根据检索条件向下寻找最靠近检索条件的记录值A作为左区间,向上寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B)。

各种加锁举例:
例如给2~25加next-lock:
(-∞,5](5,10](10,15](15,20](20,25](25,+supernum] 加锁范围是这个
数据和间隙都会加锁
在此前提下:
当有如下事务A和事务B时,事务A会对数据库表增加(10,15]这个区间锁,这时insert id = 12 的数据的时候就会因为区间锁(10,15]而被锁住无法执行。
由于间隙锁之间不互斥,所以可能会发生死锁
两个人都拿到了间隙锁,往间隙插入数据都被阻塞
事务A获取到(5,10]之间的间隙锁不允许其他的DDL操作,在事务提交,间隙锁释放之前,事务B也获取到了间隙锁(5,10],这时两个事务就处于死锁状态
2.由于数据是等值查询,并且表中最后数据id = 10 不满足id= 7的查询要求,故id=10 的行级锁退化为间隙锁,(5,10)
3.所以事务B中id=8会被锁住,而id=10的时候不会被锁住

2.由于c是普通索引,根据原则4,搜索到5后继续向后遍历直到搜索到10才放弃,故加锁范围为(5,10]
3.由于查询是等值查询,并且最后一个值不满足查询要求,故间隙锁退化为(5,10)
4.因为加锁是对普通索引c加锁,而且因为索引覆盖,没有对主键进行加锁,所以事务B执行正常
5.因为加锁范围(5,10)故事务C执行阻塞
6.需要注意的是,lock in share mode 因为覆盖索引故没有锁主键索引,如果使用for update 程序会觉得之后会执行更新操作故会将主键索引一同锁住

- next-key lock 增加范围锁(5,10]
- 根据原则5,唯一索引的范围查询会到第一个不符合的值位置,故增加(10,15]
3.因为等值查询有id =10 根据原则3间隙锁升级为行锁,故剩余锁[10,15]
4.因为查询并不是等值查询,故[10,15]不会退化成[10,15)
5.故事务B(13,13,13)阻塞,事务C阻塞

2.因为c是非唯一索引,故(5,10]不会退化为10
3.因为查询并不是等值查询,故[10,15]不会退化成[10,15)
4.所以事务B和事务C全部堵塞
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!