一、概述
锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除传统的计算资源 (CPU、RAM、I/O) 的争用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素.从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂,MySQL中的锁,按照锁的粒度分,可以分为以下三类
全局锁 : 锁定数据库中的所有表
表级锁 : 每次操作锁住整张表
行级锁 : 每次操作锁住对应的行数据
二、全局锁
全局锁就是对整个数据库实例加锁,加锁后该数据库实例下的所有表就处于只读状态,后续的 DML 的写操作,DDL 操作,已经更新操作的事务提交操作都将会被阻塞.全局锁最典型的使用场景就是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的一致性和准确性
// 加锁语法 flush tables with read lock; // 释放锁语法 unlock tables;
时间节点 | 客户端 1 | 客户端 2 | 客户端 3 |
t1 | flush tables with read lock | ||
t2 | select * from user (成功) | ||
t3 | select * from user (成功) | ||
t4 | select * from fruit (成功) | ||
t5 |
alter table fruit ... (阻塞) |
||
t6 | update ... (阻塞) | ||
t7 |
update ... (报错) |
||
t8 | unlock tables | ||
t9 | update ... (成功) | ||
t10 | alter table fruit ... (成功) |
总结 : 全局锁加锁之后,所有客户端只能读,其它操作均不支持
三、表级锁
表级锁,每次操作锁住整张表.锁定粒度大,发生锁冲突的概率最高,并发度最低.应用在 MyISAM、InnoDB、BDB 等存储引擎中,对于表级锁主要分为三类,表锁、元数据锁(metadata lock / MDL)、意向锁
3.1、表锁
表锁分类两类
表共享读锁(read lock)、表独占写锁(write lock)
// 加锁语法 lock tables 表名 read // 解锁语法 unlock tables;
时间节点 | 客户端 1 | 客户端 2 | 客户端 3 |
t1 | lock tables user read | ||
t2 | select * from user (成功) | ||
t3 | select * from user (成功) | ||
t4 | select * from user (成功) | ||
t5 |
update user set ... (报错) |
||
t6 | update user set ... (阻塞) | ||
t7 | delete from user ... (阻塞) | ||
t8 | unlock tables | ||
t9 | update user set ... (成功) | ||
t10 | delete from user ... (成功) |
总结 : 给表加上共享读锁之后,所有客户端对该表都只能读,当前客户端写入时会报错,其它客户端的写入操作会被阻塞
时间节点 | 客户端 1 | 客户端 2 | 客户端 3 |
t1 | lock tables user write | ||
t2 | select * from user (成功) | ||
t3 | select * from user (阻塞) | ||
t4 | select * from user (阻塞) | ||
t5 | update user set age = 28 where id = 4 (成功) | ||
t6 | unlock tables | ||
t7 | age = 28 (id=4) | ||
t8 | age = 28 (id=4) |
总结 : 给表加上写锁之后,只能在当前客户端进行操作,其它客户端的操作将会被阻塞
3.2、元数据锁(metadata lock)
当表上有活动事务的时候,Mysql 会自动的为该表加上元数据锁,加上了元数据锁之后就不可以对元数据进行写入操作(元数据可以简单的理解为一张表的表结构),它的主要作用是为了避免 DDL 与 DML 冲突,也就是说当表上有活动事务时不能修改表结构
// 查看元数据锁情况 select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks;
没有执行任何 Sql 语句的时候,只能查询到 performance_schema 库 metadata_locks 表的默认信息,执行 select * from user 语句后,可以看到为 user 表加上了 SHARED_READ 类型的元数据锁,执行 update user set age = 25 where id = 4 后为 user 表加上了 SHARED_WRITE 类型的元数据锁
再看下一个例子
客户端 1 开启事务,执行 select * from user 语句,为 user 表加上元数据锁 SHARE_READ
客户端 2 执行 alter table user change birthday birthdate timestamp default CURRENT_TIMESTAMP not null comment '出生日期' 语句,被阻塞
下面列举不同的 Sql 对应的元数据锁
Sql 语句 | 锁类型 | 说明 |
select、select ... lock in share mode | SHARED_READ | 与 SHARED_WRITE 兼容,与 EXCLUSIVE 排斥 |
insert、update、delete、select ...for update | SHARED_WRITE | 与 SHARED_READ 兼容,与 EXCLUSIVE 排斥 |
alter ... | EXCLUSIVE | 与其它的 MDL 都排斥 |
3.3、意向锁
为了避免 DML 在执行时加的行锁与表锁的冲突,在 InnoDB 中引入了意向锁,使得表锁不用检查每行数据是否加锁,从而减少对锁的检查次数
假设有一张 user 表,表数据如下
如果没有意向锁,当客户端 1 执行 update user set age = 30 where id = 2 这条 Sql 语句为 id = 2 的记录加了行锁之后,如果此时客户端 2 想给 user 加上表锁,那么客户端 2 就要针对 user 表的每一条记录逐行检查,查看有没有行锁,这样的效率是很慢的
有了意向锁之后,客户端 1 执行 update user set age = 30 where id = 2 为 id = 2 的记录加上行锁之后还会为 user 表加上意向锁,如果此时客户端 2 想为 user 加上表锁,那么客户端 2 不需要逐行检查,只需要根据 user 表上意向锁的类型来判断能否给 user 表加上表锁
意向锁分为两类
意向共享锁(IS)
意向排它锁(IX)
可以通过下面 Sql 来查看表的意向锁情况
// 查看意向锁情况 select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;
下面就演示一下意向共享锁(IS)、意向排它锁
Sql 语句 | 意向锁类型 | 说明 |
select * from user lock in share mode | IS | IS 与 lock tables user read 兼容,与 lock tables user write 排斥 |
insert、update、delete、select * from user for update | IX | IX 与 lock tables user read、lock tables user write 均排斥 |
四、行锁
行锁单独分开讲