一.为什么要加锁?
数据库锁机制简单来说,就是数据库在多事务并发处理时,为了保证数据的一致性和完整性,数据库需要合理地控制资源的访问规则。锁是一种资源,这个资源是和事务关联在一起的,当某个事务获取了锁,在提交或回滚之前,就一直持有该锁。
二.锁的分类
根据锁类型划分
共享锁(读锁):其他事务可以读,但不能写。
排他锁(写锁):其他事务不能读取,也不能写。
根据加锁的范围划分
全局锁、表锁和行锁三类。
全局锁
全局锁就是对整个数据库实例加锁。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。全局锁的典型使用场景是,做全库逻辑备份。
全局锁的命令:
Flush tables with read lock
表锁
MySQL 中表级别的锁有三种:表锁、意向锁和元数据锁(meta data lock,MDL)。
表锁的语法是 lock tables … read/write,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。
意向锁主要分为意向共享锁和意向互斥锁。
意向共享锁,事务想要给数据库某些行加共享锁,需要先给这张表加上意向共享锁。
意向互斥锁,事务想要给数据库某些行加互斥锁,需要先给这张表加上意向互斥锁。
意向锁主要是解决行锁和表锁的冲突问题。假设没有意向锁,事务A用行锁锁住其中一行,事务B申请表的互斥锁,然后修改整个表,行锁和表锁就会发生冲突,事务B想知道事务A锁住了哪一行,就需要遍历整张表,这是一个非常耗时的操作。引入意向锁之后,事务A在加行锁之前先给表加上意向锁,这样如果事务B来获取表锁,需要先判断表上是否有意向锁,如果有意向锁则阻塞,等待事务A的锁释放。
MDL(metadata lock)是用来保护表的元数据信息的,不需要显式使用,在访问一个表的时候会被自动加上,MDL的作用是维护数据的一致性,主要解决DML和DDL操作之间的一致性问题。
行锁
MySQL 的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如MyISAM 引擎就不支持行锁。不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。InnoDB 是支持行锁的,这也是 MyISAM 被 InnoDB 替代的重要原因之一。
行锁又分为三种,单个行记录的锁(record lock)、间隙锁(GAP Lock)、记录锁和间隙锁的组合(next-key Lock)。
三.MDL锁
为什么要引入MDL锁?
MySQL5.5引入了meta data lock,简称MDL锁,属于表锁范畴。MDL 的作用是,保证读写的正确性。你可以想象一下,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,增加了一列,那么查询线程拿到的结果跟表结构对不上,肯定是不行的。因此,当对一个表做增删改查操作的时候,加 MDL读锁;当要对表做结构变更操作的时候,加 MDL 写锁。读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。
元数据锁的使用场景模拟
会话A:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select count(1) from table_test;
+----------+
| count(1) |
+----------+
| 2439392 |
+----------+
1 row in set (7.46 sec)
会话B:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> alter table table_test add age int not null;
会话C:
mysql> show processlist;
+----+------+-----------+--------+---------+------+---------------------------------+---------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+--------+---------+------+---------------------------------+---------------------------------------------+
| 3 | root | localhost | gmdpdb | Sleep | 235 | | NULL |
| 6 | root | localhost | gmdpdb | Sleep | 138 | | NULL |
| 7 | root | localhost | gmdpdb | Query | 3 | Waiting for table metadata lock | alter table table_test add age int not null |
| 8 | root | localhost | NULL | Query | 0 | starting | show processlist |
+----+------+-----------+--------+---------+------+---------------------------------+---------------------------------------------+
4 rows in set (0.00 sec)
通过会话C可以看出会话B被阻塞,这是由于会话A拿到了table_test表的元数据读锁,会话B想申请table_test表的元数据写锁,由于读写锁互斥,会话B需要等待会话A释放元数据锁才能执行。
元数据锁可能带来的问题
Session A
Session B
Session C
begin;
select * from t;
alter table t add age int;
select * from t;
我们可以看到 session A会对表 t 加一个 MDL 读锁,之后 session B要加MDL写锁会被 blocked,因为 session A 的 MDL 读锁还没有释放,而 session C要在表 t 上新申请 MDL 读锁的请求也会被 session B 阻塞。前面我们说了,所有对表的增删改查操作都需要先申请MDL 读锁,就都被阻塞,等于这个表现在完全不可读写了。
△
▴
愉快的每一天
文章来源: blog.51cto.com,作者:MySQL技术栈,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.51cto.com/15018708/2557501