你好,看你简历中有写熟悉MySQL数据库啊,要不咱聊聊
数据库?
嗯,好啊!
我们今天聊一聊MySQL的各种锁吧,你有了解Metadata Lock吗?
嗯?(内心:这泥马)
嗯!Metadata Lock简称MDL,也就是元数据锁。
嗯,是的!说一下它有什么用处?使用它时需要显示的声明吗?
嗯,通常我不需要显示的使用这把锁,当我们对数据库表进行CRUD操作时MYSQL会自动给这个表加上元数据锁,并且这把锁会和所有企图改变表结构的SQL互斥。
说白了,元数据锁就是要保证:当有用户对表执行DML(CURD)相关操作时,其他线程不能改变把表结构,(想改表结构也可以,得等排在它前面的DML全部执行完)。反之,当有线程在更改表结构时,其他线程需要执行的DML也会被阻塞住。
嗯,那都有常见的那些操作算是在改变表结构呢?
操作 | statement |
---|---|
创建数据库 | create database |
删除数据库 | drop database |
修改数据库 | alter database |
创建表 | create table |
删除表 | drop table |
修改表 | alter table |
创建索引 | create index |
删除索引 | drop index |
嗯,如上表所示(可左右滑动),均算作修改了表结构。
好,你再说一下MySQL的表锁吧
表锁就是表级别的锁,而且InnoDB存储引擎层支持表锁。
而且上面说过的MDL锁,个人感觉也可以将元数据锁理解成是表锁中的一种。
那你能列举几个常见的添加表锁示例吗?
嗯,好啊!
# 表级别共享锁
lock tables ${tableName} read;
# 表级独占锁
lock tables ${tableName} write;
mysql> lock table t1 read;
Query OK, 0 rows affected (0.00 sec)
如何释放表锁呢?
嗯,如下:
mysql> unlock table;
Query OK, 0 rows affected (0.00 sec)
你还了解其他锁住全表的方式吗?
嗯,知道的!
# 手动开启事务
begin;
# 检索是加上共享读锁
select * from t lock in share mode;
这时其他的线程如果向insert则会被阻塞
嗯,好,我们再聊一聊MySQL的行锁吧!
Record Lock就是记录锁或者行锁。之所以叫做记录锁是因为Record Lock每次都会去锁住具体的索引记录。
锁住的是索引记录?那我的表中没有索引呢?连主键索引也不存在!
不用担心,MySQL会为你的表生成一个隐式的主键索引哦
嗯,那你举一个MySQL加行锁的例子吧!
begin;
update table set name = xxx where id = 1;
commit;
此时会对id = 1的行添加一把行锁
白日梦补充:
其实如果你使用InnoDB存储引擎的话,及时我们不显示的begin,再commit。MYSQL也会将我们的SQL放在一个单独的事务执行。
想了解这个知识点可以看这篇:全网最牛X的两阶段串讲
你了解间隙锁吗?什么是间隙锁?
间隙锁也是行锁中的一种:但是它会锁定的是一个间隙范围,而不会锁住某条记录。
好,我们来看一个间隙锁的例子:假如我有这样一张表
表中有如下数据:
但是然后我执行如下SQL,你看下会锁住哪些行?
select * from z where b = 3 for update;
嗯,不难看出:Gap-Lock会锁住(3,6)
Gap加锁的范围规律如下:
这个Gap就加在目标行(where b = 3)和下一行记录之间,且不包含这两条记录!
可以执行如下SQL会发现,SQL被阻塞住了,因为间隙被锁住了!
insert into z select 6,4;
嗯,说的还不错!我们在聊一聊Next-Key-Lock吧!你有了解吗?还是使用上一个例子,你说一说上面的例子存在Next-Key-Lock吗?如果存在的话,加锁的范围又是什么?
嗯,存在Next-Key-Lock,并且这把锁会分两步确定加锁范围,第一步如下图:
第二步:它会看我们SQL中的Where条件所用到的列是否唯一。如果具有唯一的特性的话,会将第一步确定的范围缩小成确切的某行。
拿这个例子来说,where 条件是:where b = 3,且这个b只是个普通的key,并没有唯一,所以第二步中的判断并不能将(1,3] 退化成b=3的这一行记录。
不信你可以按下面的顺序做个实验
如果我们的SQL是:
select * from z where a = 5 for update;
会怎么样呢?
由于a列是主键,所以Next-Key-Lock会从 (3,5] 退化成 a=5 这一行。
为什么会发生退化?
很简单,因为a列本身就是唯一的,不会出现多行都数据都为5的情况。换句话说,a=5 可以找到唯一的记录那我为啥还搞一个范围呢?
白日梦补充:
想了解更多的Record Lock、Gap Lock、Next-Key Lock的话可以看我专门录制的视频哦。
好,我们继续!你了解意向锁吗?
嗯,了解一点!
我们知道,在一个事务中对某个表进行增、删除、改操作时,会对被操作的行添加行锁。与此同时,其实它还会在表级别添加一个意向锁(意向独占锁)
而当一个事务中对某张表执行查询操作时,还会对该表添加一个意向共享锁。
并且:意向锁彼此之间是不会互斥的,原因很简单,当你开启事务后,无论是执行增删改给表加上了意向独占锁还是执行查询给表加上了意向共享锁,都不会影响其他事务对该表的其他行的CRUD。
但是:意向锁也并不是完全形同虚设的,因为它和除意向锁之外其他类型的锁之间是满足读读共享、读写互斥的原则的。
嗯,到现在我们也说了很多锁了,你可否总结一下各种常见锁之间的关系呢?
嗯,好啊!(可左右滑动)
锁类型 | 普通独占锁 | 普通共享锁 | 意向独占锁 | 意向共享锁 |
---|---|---|---|---|
普通独占锁 | 互斥 | 互斥 | 互斥 | 互斥 |
普通共享锁 | 互斥 | 不互斥 | 互斥 | 不互斥 |
意向独占锁 | 互斥 | 互斥 | 不互斥 | 互斥 |
意向共享锁 | 互斥 | 不互斥 | 不互斥 | 不互斥 |
了解两阶段锁和事务的两阶段提交吗?他俩有啥关系吗?谈一谈!
嗯,其实他俩其实并没有关系。
所谓的两阶段锁是说,在一个事务中锁操作分成两个阶段
* 加上锁的阶段
* 解锁阶段
嗯,那你分析一下这样图:
嗯,好啊!
上图中的事务B会被事务A阻塞住。这也反应出了两阶段锁的特性,就是这把锁会在你需要的时候加上去,但是并不是SQL执行玩了这把锁也会释放。(不然事务B中的update sql也不会被阻塞)
那什么时候两阶段锁会被释放呢?
事务commit、rollback时会释放
再问个问题:大家在开发时,无论你怎么安排一个事务中SQL的数量,锁释放的时机都是commit或者是rollback,那怎么排序能让系统的并发性能更好呢?
嗯,当然是尽量把那些更容易出现并发冲突的SQL放在事务的最后执行。这样性能更好!
哦?那你举个例子
嗯,比如有abc三条SQL,他们分别会执行1,2,3秒,并且其中c sql最容易出现并发冲突。那最优的顺序就是abc。
step1:a 执行1s
step2:b 执行2s
step3:c 执行3s
释放所有锁,此时最耗时的c仅仅锁了3s
如果你安排的顺序是cba
c 执行3s
b 执行2s
a 执行1s
释放所有锁,此时最耗时的c锁了6s
嗯,可以,你再谈一下死锁吧!有了解吗?
锁死指的是并发系统中不同的线程之间出现了资源的循环依赖,也就是说大家各自锁住了对方需要的资源,而且谁也不主动释放。夯住,出现死锁。
那出现死锁有什么解决的思路吗?
解决死锁问题有如下两种思路!
1、死锁检测
主动发起死锁检验,当发现死锁后主动回滚其中一个事务,释放掉其他线程需要的资源。通过设置参数`innodb_deadlock_detect=on`开启死锁检测。
2、等待超时
就像上面那样,出现死锁后我们不作为,双方都等待另一方先出现超时然后释放自己需要的资源。控制超时的参数是`innodb_lock_wait_timeout`默认值为50s
你知道的还挺多,整体表现还不错,我没有问题了,你还有想问我的吗?
没有问题了,感谢大佬百忙抽空来给我面试!
哈哈,客气!你应该会进入下一面的。好好准备,期待你下一面优秀的表现