浅谈数据库的锁(一)
全局锁和表锁 — server层
全局锁
顾名思义,全局锁就是给整个数据库实例进行加锁。命令是Flush tables with read lock
。
当需要整个数据库进入只读状态时,就用这个命令。一般来说,只有全局备份才会使用这个命令,但即使是做全局备份,我也不推荐使用这个锁。
因为对全局加锁,带来的坏处很多很多。
对全局进行加锁,那么基本上所有操作都会被阻塞,通常情况业务就会停摆。
如果在从库进行备份,主库传来的binlog,从库不能执行。这会加大主从数据库之间的延迟
通过Read - View来进行全局备份,效率更高。
保留全局锁的原因
为了让不支持事务的存储引擎也能够保持一致性。全局锁,每次只会让一个“事务”操作数据库。
表级锁
MySQL里面表级别的锁有两种:
- 一种是表锁
- 另一种是元数据锁 (meta data lock,MDL)。
表锁 — DML
表锁,又分为 read lock 和 write lock。表级读写锁有以下特点:
很显然跟其他读写锁一样,都是符合读读共享,读写和写写是互斥的
除此以外,还有另外 表锁不仅限制别的事务的操作,还有限制当前持有锁的事务的操作
1 |
|
MDL锁 — DDL
为什么要有MDL锁?想象一下这种场景,事务A在对表1进行查询(查询到一半),此时另外一个事务B要对表进行结构上的增加字段。
那么,我们希望查询出来的结果,一半是旧的表结构,另一半是新的表结构,多一些字段吗?显然不。
因此,就需要额外的措施去进行一下限制。但是这依靠表读写锁是做不到的。因为表读写锁,仅仅限制的是DML操作,对DDL操作没有做任何限制。
因此,需要另外一种锁去限制表的DDL操作,也就是MDL锁
MDL如何起作用?
其实它的作用原理,跟读写锁非常相似:
当事务对表的数据做 DML操作时,事务对该表加 MDL读锁。
- 因为读锁之间共享,所以不影响别的事务对表的MDL操作
当事务要对表做DDL操作时,事务对表加MDL写锁。
- 因为读写之间互斥,只要有事务在DML,那么表结构修改就会被阻塞
- 同理,只要DDL还没被提交,那么别的事务对表的DML就会被阻塞
- 这样保证了,查询出来的数据,结构都是一样的
给表加字段的坑
有时候给表加一个字段,导致整个库挂了。
- sessionA先启动,拿到MDL读锁
- sessionB启动拿到读锁,并执行
- sessionC需要修改表结构,申请写锁,被阻塞。
- sessionD想要申请读锁,但是被阻塞住了
- 个人猜测,申请锁应该是通过一个队列,类似对头阻塞的机制
因此,sessionC被阻塞其实没什么关系,但是会导致sessionC后面的所有查询事务都被阻塞了。最主要的原因就是,事务中某个语句申请一把锁。该锁是会等整个事务结束才把锁释放。
行锁 — 各自引擎具体实现
MySQL 的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如 MyISAM 引擎就不支持行锁。
不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。
两段锁协议 2PC
在这个流程中,实际上事务B仍旧会阻塞。实际上事务 B 的 update 语句会被阻塞,直到事务 A 执行 commit 之后,事务 B 才能继续执行。 为什么?
当一个事务中,具体到某个语句执行,该语句需要行锁,才会去申请
当该语句执行完毕,行锁不会马上释放,而是等到整个事务结束才会释放。
因此,我们在一个事务中,尽量把需要行锁的语句进行后置。
意向锁
行的读写锁,功能我就不详细讲了。跟普通读写锁很类似,下面我来讲一下意向锁。为什么要有意向锁?我们可以想象一些以下这个场景:
当一个事务A想对表加一个表锁。因为表锁和行锁存在一定的互斥关系,那么它就必须对全表的数据进行扫描,确定是否有行锁,如果没有行锁则进行加锁。
这个效率实在太低了,难以让人接受!
解决办法
- 如果我们在对表某个数据加行锁的时候,能不能在表上面打一个tag。
- 当别的事务想要加表锁,看到tag就知道,目前该表中存在行锁,那么加表锁的操作就会阻塞
这种解决思路就称为意向锁。
deal lock 和 检测
解决死锁的策略
- 阻塞超时等待,等待到一定时间放弃。
- 死锁检测,当发生死锁后主动回滚死锁中某一个事务,让破坏死锁的条件
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!