暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL 锁

郭相岭 2021-01-20
1542
MySQL 锁分类
只要涉及到并发,就会出现锁的概念,那么在操作MySQL时,也会遇到并发锁的问题,今天锁芯从表层浅显分析总结下。
MySQL 锁分类描述
按锁粒度全局锁对库进行上锁,锁的是整个database,由MySQL Server层实现;
表级锁锁的是表table,由 MySQL Server 层实现;MySQL MyISAM 、MEMORY 存储引擎使用表锁;
行级锁锁的是表中的某行数据,也能锁定行之间的间隙;MySQL InnoDB 存储引擎使用行锁,也支持表锁;
页面锁页面锁,比如 MySQL BDB存储引擎采用的是页面锁,也支持表锁;基本上被InnoDB存储引擎取带;
按锁功能 表共享读锁读锁共享,并且可以上多次读锁;
表独占写锁只要上了写锁,不能再上读 或写锁;
按锁实现方式悲观锁表级锁
(MyISAM)
表锁(手动)加读锁后,可以加再加读锁,不能加写锁;
加写锁后,不能再加读锁,也不能再加写锁;
元数据锁(自动)CURD(create/update/read/delete)加读锁,DDL加写锁
意向锁(innoDB)MySQL内部使用,表共享读锁、表独占写锁
表锁特点开锁小,加锁快,不会出现死锁,锁粒度大,发生锁冲突率高,并发度低,适合以检索查询为主,少量按索引更新数据的应用场景;
MyISAM引擎引擎的读写锁互斥,并且写锁优先级比读锁高,即使读锁请求先到达等待队列,写锁请求达到后, 也会插入到读锁的前面。这也是MyISAM存储引擎不适合写多读少的应用的原因,不然如果写一直有,读将会阻塞;但可能参数设置读优先或者写优先;
行级锁(innoDB)表共享读锁(手动)如:select ... lock in share mode
表独占写锁(自动)如:DML(insert/update/delete),select ... for update
行锁特点开锁大,加锁慢,会出现死锁,锁粒度最小,发生冲突概率要低,并发度高;目前广泛应用;
乐观锁一般是由程序实现,比如根据版本号或者时间戳来实现的。


表锁

表锁是由MySQL Server层实现的,可能通过show status like 'table%'; 来查看表的状态,show open tables; 查看表锁情况;

Table_locks_waited,指的就是存在多少张表阻塞等待中;


MyISAM 存储引擎写锁测试

操作顺序session Asession b备注
11. lock table test write;
2. select * from test;
3. insert into test(name,age)values("lisi",50);

在SessionA中锁表,查,插入均成功;
2
1. select * from test;在Seesion B中查询阻塞;
34. unlock tables;阻塞
4

sessionB中的,1步骤成功返回;

操作过程如下

其实在写锁过程中,也不能对其它锁进行操作,类似下面读锁演示的读其它表;


MyISAM 存储引擎读锁测试

操作顺序session Asession b备注
11. lock table test read;
2. select * from test limit 1;

session A加读锁
2
1. select * from test limit 1;session 可以读;
33. select * from test2 limit 1; 报错;
4. update test set name='k8svip' where id = 1; 报错
2. select * from test2 limit 1; 在read锁表session A中,只可读自己的锁定的表,不能读其它表,并且也不能更新锁定的读表;session B中可以任意读;
4
3. update test set name='k8svip' where id = 1;阻塞中,表在sesson A中处于锁定状态,其它session 是无法正行写操作的;
55. unlock tables;继续阻塞
6
4. 完成第3步中的更新操作;



元数据锁

元数据锁是在MySQL 5.5 引入了MDL,当对一个表做增删改查操作的时候,加MDL读锁,当要对表做结构变更操作的时候,加MDL写锁,MDL元数据锁不能显式的使用,它在访问一个表的时候会被自动加上。

MDL元数据锁的作用是,保证读写的正确性,你可以想象一下,如果一个查询正在遍历一个表中的数据,而此时进行表结构的修改,增加或者删除一列,查询线程拿到的结果跟表结构对不上,肯定是不行的。

读锁之间不互斥,因此你可以有多个线程同时对一张表进行查看。

读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。

演示说明,由于查询语句查询完成后,就结束了,也就是说MDL读锁就被释放掉了,为了构造演示环境,可以先开启一个事务,在开启一个事务之后,如果不人为的结束一个事务,事务里面的SQL语句,比如select时,他会一直持有MDL的读锁,直到你 commit 或者 rollback 事务结束时,才释放MDL锁;

操作顺序session Asession B备注
11. begin; 或者  start transaction
2.  select * from test2;


2
  1.  select * from test2;

  2. alter table test2 add f int;

Session B 步骤1select 正常,但修改元数据时会阻塞。
33. commit; 或者rollback

4
在SessionA commit后,Session B 阻塞消失,修改成功;



行锁

MySQL 行锁,是利用存储引擎锁住索引项来实现,由于是根据索引项来实现行锁的,那么意味着,只有通过索引条件检索的数据,InnoDB才使用行级锁,否则InnoDB将使用表锁,这里主要总结 InnoDB 存储引擎的行级锁,查看行级锁使用状态show status like 'innodb_row_lock%';

当Innodb_row_lock_waits 等待次数很高,而且每次Innodb_row_lock_time也很长的时候,就需要分析为什么会有如何多的等待,然后根据分析结果进行优化。

对于update、delete和insert 语句,InnoDB会自动给涉及数据集加排他锁(x),而对于普通的select 语句,InnoDB不会加任何锁,事务可以通过以下语句显示的给记录集添加共享锁和排他锁;

InnoDB锁分类小类别说明
行锁按锁定范围记录锁或叫行锁锁定索引中的一条记录;
间隙锁要么锁住索引记录中间的值,要么锁住第一个索引记录前面的值或者最后一个索引记录后面的值。
Next-Key Locks它是索引记录上的记录锁和在索引记录之前的间隙锁的组合;它是记录锁和间隙锁的总称。
按功能共享读(S)允许一个事务去读一行,阻止其他事务获得相同数据集的排他写;一个事务读时,其它事务不能写;共享读,指的是加上select * from table_name lock in share mode;
排他写(X)允许获得排他写锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁(不是读)和排他写锁;排他写,指的是加上select * from table_name where ... for update;
表锁又称意向锁,MySQL内部实现,不需要用户干预意向共享锁(IS)事务打算给数据行加行共享锁(S),事务在给一个数据行加共享锁前必须先取得该表的IS锁;
意向排他锁(IX)事务打算给数据行加行排他锁(X),事务在给一个数据行加排他锁前必须先取得该表的IX锁;


意向锁和行锁可以共享,意向锁的主要作用是为了全表更新数据时的性能提升,否则全表更新数据时,需要先检索该表是否某些记录上面有行锁;


共享锁 S排他锁 X意向共享锁 IS意向排他锁 IX
共享锁 S兼容冲突兼容冲突
排他锁 X冲突冲突冲突冲突
意向共享锁 IS兼容冲突兼容兼容
意向排他锁 IX冲突冲突兼容兼容


行读锁演示

操作顺序session Asession B备注
11. begin;
2.  select * from test3 where id = 1 lock in share mode;

session A 中加行读锁;
2
1.update test3 set name = "kkvv" where id=2;
2. update test3 set name = "kkvv1" where id=1;
Session B阻塞中 1,是可以修改的;2 处于阻塞中,超过一定时长,会报错;
33. commit;

4
在SessionA commit后,Session B 阻塞消失,修改成功;


行读锁示例


行读锁升级为表锁

操作顺序session Asession B备注
11. begin;
2.  select * from test3 where name="kkvv" lock in share mode;

session A 在加读锁,未使用索引;
2
1.update test3 set name="k8svipvip" where id=1;Session B 更新其它字段也不可以,因为在session A中 where 条件中没有使用索引,此时一旦加锁,就变为了表锁,所以导致session B中,即使更新其它字段也不可以的原因。
33. commit;

4
在SessionA commit后,Session B 阻塞消失,修改成功;


行读锁升级为表锁示例


行写锁

操作顺序session Asession B备注
11. begin;
2.  select * from test3 where id=1 for update;

session A加行写锁,注意这里索引是主键,行记录锁;
2
1. select * from test3 where id=2;
2. select * from test3 where id=1;
3. select * from test3 where id=1 lock in share mode;
sessionB 中 1,2 都不会阻塞,只有在lock in share mode共享锁时,才会阻塞;
33. commit;

4
在SessionA commit后,Session B 阻塞消失,修改成功;


行写锁示例


间隙锁 

间隙锁产生于 MySQL 事务隔离级别的 RR(默认隔离级别),间隙锁锁的封。遇到有一个非唯一索引,我们对表建立一个普通索引,如下所示:


在非唯一索引加行写锁产生间隙锁案例

操作顺序session Asession Bsession C备注
11. begin;
2.  select * from test3 where age = 19 for update;


session A,在非唯一索引上面加行写锁
2
1. begin;
2.insert into test3 values(21,"k8s",37,1);
3. insert into test3 values(2,"k8s",17,1);

sessionB 中  2 都不会阻塞,因为不在间隙锁内,3为阻塞,因为在间隙锁内;
3

1. insert into test3 values(4,"k8s",18,1);不管是否开启类型Session B事务,都会阻塞;
43. commit;


5
在SessionA commit后,Session B 阻塞消失,插入成功;


1. 我们锁定的是 id为3 age 为19行的上面间隙;即ID为 1与3之间,3与5之间,age为17与19,19与20之间,注意以上例子中锁定的是age从17到19,19到20;

2. 想要插入的数据同时满足上面17<<age<<20间隙时,是不可以插入进去的;

3. 如 session B 中 insert into test3 values(21,"k8s",37,1); 在间隙的外面,是可以插入的,不会阻塞;

4. session B 中的 insert into test3 values(2,"k8s",17,1); 在间隙内,是不可以插入的,都会阻塞,超时会失败;

5. 同理,在Session C 中,insert into test3 values(4,"k8s",18,1); 也在间隙内,也是不可以插入的,都会阻塞,超时会失败;

以上实验是非唯一索引时,加行写锁时,会产生间隙锁 ;


下面间隙锁失败

如上图中的间隙锁就不生效了,我们本意加间隙锁ID 5到8,8到13,age 20-16,16到26;由于age没有20-->16的,间隙锁不生效;


在主键索引加行写锁产生间隙锁案例

操作顺序session Asession Bsession C备注
11. begin;
2. select * from test3 where id >2 and id < 13 for update;


session A,在主键索引上面加行写锁时,该区间内不能再插入数据;
2
1. begin;
2.insert into test3 values(7,"k8s",19,1);

sessionB 中  阻塞中
3

1. insert into test3 values(10,"k8s",5,1);session C 也是阻塞中;
43. commit;3 commit;

5
在SessionA commit后,Session B 阻塞消失,插入成功;

间隙锁用例


当索引的间隙锁值不存在时怎么办?

操作顺序session Asession Bsession C备注
11. begin;
2.select * from test3 where age=50 for update;


session A中没有这个非唯一索引时,会如何呢?
2
1. begin;
2. insert into test3 values(25,"k8s",6,1);
3. insert into test3 values(26,"k8s",45,1);

age大于41的都会阻塞,小于41的都可以插入成功;
距离最近的age的数值做为间隙锁的左起点,右边无穷大
41<<age<<∞ 都会阻塞
3

1.  insert into test3 values(27,"k8s",51,1);也会阻塞
43. commit;


5
在SessionA commit后,Session B 阻塞消失,插入成功;在SessionA commit后,Session C 阻塞消失,插入成功;

间隙锁值不存在示例


两阶段锁

传统RDBMS加锁的一个原则,就是2PL(Two-Phase Locking,二阶段锁)。锁的操作分为两个阶段,加锁和解锁阶段,并且保证加锁阶段和解锁阶段不相交。加锁阶段:只加锁,不放锁;解锁阶段:中放锁,不加锁;例如在事务操作中:


死锁

两个或多个Session 互相等待对方的资源释放后,才能释放自己的资源,造成了死锁,我等你,你等我的状况,它是因为顺序造成的死锁现象;

操作顺序session Asession B备注
11. begin;
3.update test3 set name="abc" where id =1;
5. update test3 set name="abc" where id =24;

session A 、B 中开启事务;
session A中更新id=1,session B中更新id=24,此时都没有问题;
session A中更新id=24时,就会阻塞,当session B中再去更新id=1时,MySQL自动检测出死锁状态,造成死锁的语句直接崩溃;session A执行成功;
2
2. begin;
4.  update test3 set name="bdc" where id=24;
6. update test3 set name="bdc" where id=1;


死锁示例


总结

详细总结了 MyISAM 存储引擎表读锁、表写锁、元数据锁是怎么回事,InnoDB的行读锁、行读锁在什么情况下升级为表锁,行写锁、间隙锁、间隙锁在什么情况下会失败、当索引的间隙锁值不存在时怎么办?行锁解决了并发问题,间隙锁和行锁如何解决了幻读问题,请查看:MySQL 事务



您的关注是我写作的动力



文章推荐


讲讲 tcp_tw_recycle,tcp_tw_reuse

通俗易懂理解Kubernetes核心组件及原理

VxLAN 与 Bridge、Namespace基础

通俗易懂理解 MySQL B+树、数据存储、索引等知识

MySQL 事务


基础小知识


hping 命令使用小结

Linux 网卡 bonding 小知识

centos7 安装 bcc-tools 软件包

Centos7.6内核升级


专辑分享


kubeadm使用外部etcd部署kubernetes v1.17.3 高可用集群

第一篇  使用 Prometheus 监控 Kubernetes 集群理论篇

Ceph 基础篇 - 存储基础及架构介绍


参考:《高性能MySQL》

最后修改时间:2021-01-20 12:42:12
文章转载自郭相岭,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论