1.1 从粒度上分
全局锁
-
共享锁(读锁):会阻塞写锁
-
排他锁(写锁):会阻塞读锁,写锁
使用场景:全库导出,全库备份,需要保证整个数据库一致性
用法:
flush tables with read lock 添加读锁
Unlock tables 释放全局锁
另外:-single-transaction 可以通过将导出操作封装到一个事务中,使得导出的数据是一个一致性快照(需要在支持MVCC的场景)
表锁
-
表共享读锁
-
表独占写锁
在MyISAM引擎中,读写操作会分别自动加上读写的表锁。在写操作多的情况下,性能很差。
Mysql会触发表锁的指令:
Alter table:更改表结构
Drop table 和 truncate table:删除整个表/删除表中所有数据
Lock table table_name write/read:显示加锁
全表扫描
行锁
-
共享锁(S锁):select...for share
-
排他锁(X锁):select...for update,insert,update,delete
查看数据库中的锁:select * from performance_schema.data_locks;
注意:
-
行锁只在事务中有效,事务提交之后才会解锁;
-
行锁是通过对索引加锁实现的,而不是对表记录加锁,也就是如果没有命中索引就会锁全表;
-
直接select不加锁,写锁阻塞的是读写锁 而不是读写操作,如下面这种场景,事务2是能读到数据的
表结构(后面的举例都复用该表):
id | name | age |
1 | zhangsan | 14 |
3 | lisi | 20 |
事务1 | 事务2 |
Start transation | Start transation |
Select * from user where id = 1 for update | |
Select * from user where id = 1 | |
commit | commit |
1.2 从模式上区分
乐观锁
一般通过在表中加一个version列记录版本号,修改的时候进行CAS
悲观锁
select...for share:加读锁,无法在改行再加写锁
select...for update:加写锁,无法在该行再加读锁或者写锁
1.3 其他
意向锁(表锁)
当事务A持有行锁时,mysql会为该表加意向锁;事务B如果想申请整个表的写锁,就不用遍历每一行判断是否有行锁存在,而是直接判断有没有意向锁即可
间隙锁,记录锁,临键锁(行锁)
间隙锁锁的是不存在的数据,记录锁锁的是具体的某一行,在innodb的可重复读隔离级别下,间隙锁和记录锁组成临键锁,下面这种场景,临键锁锁的就是[10,20](age中小于20的第一个数到20)
事务1 | 事务2 |
Create idx_age on user(age) | |
Start transation | Start transation |
Select * from user where age=20 for update | |
insert into user value(2,'wangwu',15); 【插入被阻塞】 | |
commit | commit |
注意:
只有非唯一索引上存在临键锁,在唯一索引列不存在临键锁(包括主键列),在下面场景中,事务二是能正常插入的,这时候的幻读是靠MVCC解决的
事务1 | 事务2 |
Start transation | Start transation |
Select * from user where id = 3 for update | |
insert into user value(2,'wangwu',30); 【能正常插入】 | |
Select * from user 【读不到id为2的数据】 | |
commit | commit |
参考视频:https://www.bilibili.com/video/BV1po4y1M7k5/?p=3&spm_id_from=pageDriver&vd_source=986015d7427e83da4b4f898ee289dd8b