锁
全局锁
全局锁就是对整个数据库实例加锁
MySQL 提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。
使用场景
全库逻辑备份(保证备份与逻辑一致)
1 | 因为不同表之间的执行顺序不同进而备份的时间不同。如果某个表在这个时间差中进行了更新并且成功被备份,而与其有关联的表已经在之前备份完毕已无法更新。此时就发生数据不一致。 |
表级锁
表锁
对表加读锁后,自己也不能对其进行修改;自己和其他线程只能读取该表。 当对某个表执加上写锁后(lock table t2 write),该线程可以对这个表进行读写,其他线程对该表的读和写都受到阻塞;
元数据锁(MDL)
元数据锁是server层的锁,表级锁,主要用于隔离DML和DDL操作之间的干扰。每执行一条DML、DDL语句时都会申请MDL锁,DML操作需要MDL读锁,DDL操作需要MDL写锁(MDL加锁过程是系统自动控制,无法直接干预,读读共享,读写互斥,写写互斥)
读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。
读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
申请MDL锁的操作会形成一个队列,队列中写锁获取优先级高于读锁。一旦出现写锁等待,不但当前操作会被阻塞,同时还会阻塞后续该表的所有操作。事务一旦申请到MDL锁后,直到事务执行完才会将锁释放。
使用场景
ABCD4个session 同时进来但是有先后执行 AB都是DML操作拿到了MDL读锁 但是没执行完毕 然后C是DDL操作需要MDL写锁这时候是拿不到MDL写锁的因为AB的读锁没释放 读写互斥。 然后D是DML操作这时候也是拿不到锁的因为写锁在等待中所以D被堵塞了
但是以上仅仅为理论
实际效果
C D被 block没毛病。 但是A B commit后C还是Block的状态, 需要D commit后C才会继续。
这个问题就要涉及到online DDL了
由于DDL执行时如果锁表的话会严重影响性能,不锁表又难搞定操作期间DML语句的影响,于是MySQL推出了全新的online DDL概念,即通过:
- 拿MDL写锁
- 降级成MDL读锁
- 真正做DDL
- 升级成MDL写锁
- 释放MDL锁
第二步拿读锁是为确保没有其他DDL语句在执行;第三步是自己申请一块空间开始改表结构、填数据;等填好了之后,执行第四步,这期间由于持有读锁,可以确保不会有其他DDL语句造成不一致性;最后等拿到写锁,把表一替代就搞定了。
上述session A,B commit后,sessionC确实拿到了写锁,只不过由于锁降级,令sessionD拿到了读锁。但session D没有commit,因此session C在执行online commit到第三步后,又给阻塞了。所以就出现了类似于“插队”的现象。
优雅的解决方案
1、解决长事务,事务不提交,就会一直占着 MDL 锁。要考虑先暂停 DDL,或者 kill 掉这个长事务。
2、比较理想的机制是,在 alter table 语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到 MDL 写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者 DBA 再通过重试命令重复这个过程。MariaDB 已经合并了 AliSQL 的这个功能,所以这两个开源分支目前都支持 DDL NOWAIT/WAIT n 这个语法。
行锁
行锁就是针对数据表中行记录的锁,通过锁索引记录实现
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
所以在高并发的业务中应该把热点操作需要修改同行数据的语句尽可能的放在事务的后面,减少事务之前的锁等待。
使用场景
事务 A 更新了一行,而这时候事务 B 也要更新同一行,则必须等事务 A 的操作完成后才能进行更新
死锁
当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。
产生场景
事务 A 在等待事务 B 释放 id=2 的行锁,而事务 B 在等待事务 A 释放 id=1 的行锁。 事务 A 和事务 B 在互相等待对方的资源释放,就是进入了死锁状态。