网站制作素材图片,怎么判断一个网站做的好不好,没有网站怎么做排名优化,网站建设进程方案我们主要从三个方面来讨论这个问题#xff1a;
啥时候加#xff1f;如何加#xff1f;什么时候该加什么时候不该加#xff1f;
1、啥时候加 1.1 显式锁
MySQL 的加锁可以分为显式加锁和隐式加锁#xff0c;显式加锁我们比较好识别的#xff0c;因为他往往直接体现在 S…我们主要从三个方面来讨论这个问题
啥时候加如何加什么时候该加什么时候不该加
1、啥时候加 1.1 显式锁
MySQL 的加锁可以分为显式加锁和隐式加锁显式加锁我们比较好识别的因为他往往直接体现在 SQL 中常见的显式加锁语句主要有
▶︎ select ... for update
▶︎ select ... in share mode两者的区别在于前者加的是排它锁后者加的是共享锁。加了排他锁之后后续对该范围数据的写和读操作都将被阻塞另外一个共享锁不会阻塞读取而是阻塞写入但是这往往会带来一些问题比如电商场景下更新库存时候我们为了保障数据的一致性更新往往需要先将该商品数据锁住如果此时两个线程并发更新库存就可能会导致数据更新出现异常。
所以我们在业务上往往会使用 select … for update 对数据进行加锁。另外还有些咱们比较不常用的加锁方式比如
全局锁Flush tables with read lock主要在进行逻辑备份的时候会用到表锁lock tables … read/write
1.2 隐式锁
隐式锁是我们需要特别关注的很多的“坑”就是因为隐式锁的存在导致的无形往往最为致命。
表级锁除了表锁以外还有元数据锁
▶︎ 在进行增删改查的时候会加 MDL 读锁
▶︎ 在对表结构进行变更的时候会加 MDL 写锁
这个会带来的问题就是当我们想给表添加索引或者修改表结构的时候由于加了 MDL 写锁会阻塞我们线上正常的读写请求这个时候可能会触发上游的失败重试机制那很可能就会出现请求雪崩导致 DB 被打挂。
另外的就是与我们日常业务息息相关的行锁以及间隙锁当我们在进行增删改的时候会根据当前的隔离级别加上行锁或者间隙锁那么这时候需要注意是否会影响正常业务的读写性能另外带来的风险就是可能出现加锁范围过大阻塞请求并触发上游重试导致服务雪崩DB 打挂。
1.3 会不会加锁呢
谈到这里有的同学可能有疑问你这增删改都加锁了那我读的时候岂不是性能很差特别是在读多写多的业务场景下我的读请求一上来的话DB 不是分分钟被我查挂了其实这里 innodb 引擎用到了一个 mvcc 的技术即多版本并发控制其原理就是在数据更新的同时在 undolog 中记录更新的事务 id 以及相应的数据并且维护一个 Readview 的活跃事务 id这样当一个事务执行的时候很容易能知道自己能看见什么数据不能看见什么数据这时候读取数据自然也就不会受到锁的影响能够正常地读取啦。
2、怎么加 这里讨论怎么加其实就是了解加锁的类型以及范围即用了什么锁且加在哪里了在讨论这个问题之前我们先来看看事务隔离级别
▶︎ 读未提交
▶︎ 读已提交
▶︎ 可重复读
▶︎ 串行化
为啥要说这个呢因为隔离级别也影响着咱们的加锁读已提交解决了脏读的问题但是未解决幻读问题可重复读通过引入间隙锁解决了幻读问题因此意味着不同的隔离级别用到的锁还不一样但是有一点明确的是越高隔离级别锁的使用更加严格。可重复读是默认的事务隔离级别但是线上设置的隔离级别往往都是读已提交主要是因为这个级别够用并且能够有更好的并发性能。接下来我们讨论的范围也主要是在读已提交RC和可重复读RR。
这里根据相应规则来具体分析
▶︎ 原则1加锁的基本单位是 next-key lock。希望你还记得next-key lock 是前开后闭区间。
▶︎ 原则2查找过程中访问到的对象才会加锁。
▶︎ 优化1索引上的等值查询给唯一索引加锁的时候next-key lock 退化为行锁。
▶︎ 优化2索引上的等值查询向右遍历时且最后一个值不满足等值条件的时候next-key lock 退化为间隙锁。
▶︎ 一个 bug唯一索引上的范围查询会访问到不满足条件的第一个值为止。
另外有两点需要注意的是
▶︎ 锁是加在索引上的
▶︎ gap锁是共享的而非独占的。
2.1 RC
接下来分别进行讨论可能有些冗长需要你耐心看完。
首先是 RC 级别这个级别下的加锁规则是比较简单的因为只涉及到行锁首先我们先设计一张表
CREATE TABLE t_db_lock (id int(11) NOT NULL,a int(11) DEFAULT NULL,b int(11) DEFAULT NULL,PRIMARY KEY (id),KEY a (a)
) ENGINEInnoDB;
insert into t_db_lock values(0,0,0),(5,5,5),(10,10,10);2.2 主建等值存在 ▶︎ 可以看到此时 sessionA 在做主键上的数据更新将当前的记录的主键值更新为1此时 db 会在 id1 和 0 上加上行锁即此时针对该id的更新会被阻塞
▶︎ 因此当 sessionB 想插入 id1 的记录时会被阻塞住
▶︎ 但是由于 sessionC 更新的是 id5 的记录因此可以执行成功。
2.3 非唯一等值 ▶︎ sessionA 根据普通索引的判断条件更新数据由于行锁是加在索引上因此这时候 a 列相关索引数据上了锁
▶︎ 但是为啥这时候我更新 id0 的数据也被阻塞了呢因为这时除了加 a 上的索引还有回表更新的操作此时访问到的主键上的索引也会被加锁因为是同一行所以此时更新同样被阻塞住
▶︎ 同样的道理当我们去更新的 b0 的数据对应的主键索引上也是同一条数据所以此时更新也被阻塞但是如果我们此时是更新 b5 的这条数据的话就能更新成功。
2.4 主键等值不存在 ▶︎ sessionA 加了一个 id 为2的锁此时这行记录不存在行锁没有加成功因此不会阻塞其他 session 的请求
▶︎ sessionB 执行成功
▶︎ sessionC 执行成功。
2.5 无索引等值不存在 ▶︎ 这种情况和主键等值不存在一致由于未找到对应的加锁记录则后续的更新操作都能够执行成功。
2.6 主键范围 ▶︎ sessionA 根据范围加锁锁了 id0 和 5 这两行数据
▶︎ sessionB 由于更新 id0 这行已经上锁的数据所以被阻塞住
▶︎ sessionC 由于之前 id1 这行记录并不存在所以可以正常插入这个场景是不是有点熟悉就是咱们所说的幻读如果这时候在 sessionA 中再执行 select * from t_db_lock where id 0 and id 5 就会发现多了一条数据
2.7 RR
这里可重复读级别下主要是讨论间隙锁的加锁场景这种加锁情况会比读已提交的隔离级别复杂的多set session transaction isolation level repeatable read。
2.8 主键等值存在 ▶︎ sessionA 在已经存在的 id5 这行加锁根据加锁规则唯一索引会退化为行锁因此仅在 id5 这行加锁其实这也好理解既然已经是唯一索引了那么就不会会出现幻读的情况因此幻读仅仅取决于这行是否存在因此我只要给该行加锁保证不再写入即可
▶︎ sessionB 和 sessionC 均不在锁范围内则插入成功.
2.9 非唯一等值 ▶︎ sessionA 在已经存在的 a5 这行记录上加锁由于是非唯一索引根据加锁规则首先扫描 a 索引加上 next-key lock (0,5] ,接着向右遍历到第一个不满足条件的(根据规则五唯一索引上的范围查询会访问到不满足条件的第一个值为止)并退化为间隙锁因此加锁范围为5,10总体加锁范围为0,10并且 for update也会对应在主键的索引范围内加上锁即010
▶︎ sessionB 在主键索引的锁范围内因此被阻塞
▶︎ sessionC 此时不在普通索引和主键索引的范围上因此执行成功
这里可以看到对于非唯一等值查询的情况下加锁的范围要比主键等值存在更大因此我们在对非唯一索引加锁的时候需要注意这个范围。
2.10 主键等值不存在 ▶︎ sessionA 此时对 id3 的记录加上了行锁但是由于此时3这行的记录不存在会对此范围加锁按照加锁原则向右遍历且最后一个值不满足等值条件next-key lock 退化为间隙锁此时加锁范围为0,5
▶︎ sessionB 属于加锁范围内因此被阻塞
▶︎ sessionC 不在此加锁范围内加锁成功。
为啥这里要加的是范围锁呢其实主要解决的是幻读问题假设这里没有在此范围内加锁那么 T1 时刻 sessionB 执行成功T2 时刻再次执行 select * from t_db_lock where id 3 的话就会发现原先查询不到的结果现在竟然可以查询到了就像出现幻觉一样为了避免出现这种幻读的情况需要在此范围内加锁。
2.11 非唯一等值不存在 ▶︎ sessionA 在 a3 这行上加锁的由于 db 中不存在该行所以同样会加next-key lock,并且因为锁都是加在索引上的因此会在 a 索引上加上0,5的范围锁。但是这里有个奇怪的现象当 a5 时如果 id5 会阻塞如果 id5 则会成功从结果看来此时 a 上的锁似乎是有偏向性的并不是严格意义上的 a5 时就会锁住相应的插入记录
2.12 主键范围 ▶︎ sessionA 进行范围查询加锁在语义上等价于 select * from t_db_lock where id 5 for update但是实际加锁情况还是有很大的区别首先 id 5 根据等值查询查询到id5这行加锁为0,5]由于是唯一索引退化为行锁因此在 id5 这行上加了锁接着向右查询找到第一个不满足条件的值即 id10 这行所以加 next-key lock(5,10]这里因为并不是等值查询不会有退化为间隙锁的过程所以整体加锁范围[5,10]
▶︎ sessionB 不在锁范围内插入成功
▶︎ sessionC 在锁中插入失败注意这里是被阻塞住而不是报主键冲突。
2.13 非唯一范围 ▶︎ sessionA 加锁范围区别于主键索引主要是在(0, 5]这个范围下并未退化为行锁因此总体加锁范围为(0, 10]
2.14 无索引等值不存在 ▶︎ sessionA 中加锁记录为 b6 这行由于 b 未创建索引因此会将所有 b 索引上的记录都加锁由于是 for update 加锁认为还回去主表上更新因此主表的相关记录也都被上了锁这就会导致加锁期间处于锁表的状态任何的更新操作都没办法成功这在线上会是非常危险的操作可能会导致 db 被打垮。
3、什么时候该加什么时候不该加
通过上述的分析我们应该对锁的类型以及语句中加锁的范围有一个大致的了解可以知道悲观锁是需要我们谨慎使用的因为很可能简单的 SQL 就会拖垮 db 的性能影响线上服务的质量那么什么时候该加什么时候不该加呢
我认为对于 db 的并发场景我们可以这么去考虑
▶︎ 尽可能优先考虑使用乐观锁的方式解决
▶︎ 如果需要用到悲观锁则务必在加锁的键上加索引
▶︎ 确认 db 的隔离级别分析 SQL 中可能存在导致冲突或者死锁的原因避免 SQL 被长时间阻塞
其实对于 db 的互斥方案并没有银弹要根据具体的业务场景去针对性的制定解决方案只是在可能出现的一些坑中我们能够提前识别到避免低级错误并且有能力去优化他这就是能让自己不断进步提升的好方法啦。
学习资料点此下载