全局锁
即对整个数据库实例加锁,flush tables with read lock,该命令让整个库处于只读状态,数据库的增删改查和DDL语句都会阻塞
使用场景
在对数据库做逻辑备份的时候,例如:mysqldump做逻辑备份时默认会加全局锁。当我们在执行逻辑备份时加上–single-transaction(只能支持所有表使用事务引擎的库)参数时默认会开启一致性读,而由于MVCC的支持这个过程中数据可以正常更新
优点
保证数据一致性。例如:用户购买商品,先保存订单表,然后扣减余额。在用户未购买商品时开始进行逻辑备份(如果不上锁),此时已经备份好了订单表,还未备份余额表,用户完成购买操作(订单表增加记录、余额表扣减余额),此时再对余额表进行备份,就会导致余额和订单信息不一致问题
缺点
阻塞业务操作。如果在主库上执行,那么主库无法正常写入
- 主从延迟。在从库上执行,导致从库无法同步主库上传入的binlog
表级锁
表锁
lock tables … read/write,也可以用unlock tables主动释放锁。在限制其他线程的读写同时,也限制了本线程接下来的操作元数据锁(MDL)
MDL不需要显示使用,在访问一个表的时候会被自动加上。
对一个表增删改查的时候会上MDL读锁,在对表结构进行变更(变更表结构或加索引)时会上MDL写锁。读-读不互斥,读-写互斥行锁
MySQL的行锁是通过各个引擎自己实现的。为了防止并发修改数据导致脏读,所以当我们在执行锁定读的语句时会进行上锁,并且直到事务提交才释放锁。锁定读语句:select … lock in share mode,select … for update,update…,delete…
例如下表
| c1 | c2 | c3 | | :—-: | :—-: | :—-: | | 1 | 4 | u | | 3 | 9 | d | | 4 | 4 | a | | 5 | 3 | y | | 9 | 11 | u | | 15 | 12 | z |create table index_demo(c1 int,c2 int,c3 char(1),primary key (c1),index idx_c2(c2))
RU&RC
在读未提交和读提交的隔离级别下,会为索引扫描过且满足条件的记录上锁
例如:针对上表进行update index_demo set c3 = ‘test’ where c1 > 1 and c1 < 15 and c2 > 3
- 根据聚簇索引(c1)找出第一条c1 > 1 and c1 < 15的记录(3,9,d),并对该记录上锁
- 判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
- 满足,不释放锁,返回给server层,寻找下一条(4,4,a)并对该记录上锁
- 判断是否满足c1 > 1 and c1 < 15 并对该记录上锁
- 满足,判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
- 满足,不释放锁,返回给server层,寻找下一条(5,3,y)并对该记录上锁
- 判断是否满足c1 > 1 and c1 < 15
- 满足,判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
- 不满足,释放该记录的锁,寻找下一条(9,11,u)并对该记录上锁
- 判断是否满足c1 > 1 and c1 < 15
- 满足,判断是否满足c1 > 1 and c1 < 15 and c2 > 3
- 满足,不释放锁,返回给server层,寻找下一条(15,12,z)并对该记录上锁
- 判断是否满足c1 > 1 and c1 < 15
- 不满足,释放该记录的锁,返回给server层 “查询完毕”
RR&Serializable
在可重复度和串行化的隔离级别下,会对索引扫描过的记录上锁并且为扫描过的间隙上锁
例如:针对上表进行update index_demo set c3 = ‘test’ where c1 > 1 and c1 < 15 and c2 > 3
- 根据聚簇索引(c1)找出第一条c1 > 1 and c1 < 15的记录(3,9,d),并对该记录上锁
- 判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
- 满足,不释放锁,返回给server层,寻找下一条(4,4,a)并对该记录上锁
- 判断是否满足c1 > 1 and c1 < 15 并对该记录上锁
- 满足,判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
- 满足,不释放锁,返回给server层,寻找下一条(5,3,y)并对该记录上锁
- 判断是否满足c1 > 1 and c1 < 15
- 满足,判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
- 不满足,不释放该记录的锁,寻找下一条(9,11,u)并对该记录上锁
- 判断是否满足c1 > 1 and c1 < 15
- 满足,判断是否满足c1 > 1 and c1 < 15 and c2 > 3
- 满足,不释放锁,返回给server层,寻找下一条(15,12,z)并对该记录上锁
- 判断是否满足c1 > 1 and c1 < 15
- 不满足,不释放该记录的锁,返回给server层 “查询完毕”
间隙锁
幻读
幻读是指在一个事务中用一样的SQL多次查询,结果每次查询的结果都发现了一些之前没有看到过的数据。例如:如下场景,如果在序号1处没有读到c1=13的记录,但是在c4却读到了c1=13的记录,那么就是幻读。MySQL是通过MVCC + 间隙锁机制解决的幻读问题
| 序号 | 事务A | 事务B |
|---|---|---|
| 1 | select * from index_demo where c1 > 1 and c1 < 15 | |
| 2 | insert into index_demo c1 = 13 | |
| 3 | commit | |
| 4 | select * from index_demo where c1 > 1 and c1 < 15 |
MVCC
MVCC是指MySQL为减少读写并发为一个记录生成多个版本。其可以在一定程度上解决幻读问题。上述案例中事务A在序号1处读取数据库的记录时会生成一个一致性视图,此时假设事务B可以正常提交,那么事务A在步骤4的时候由于使用的是在序号1处开启的一致性视图(此时事务B还未提交),所以虽然事务B已经提交了,但是依旧读不到c1=13的记录
| 序号 | 事务A | 事务B |
|---|---|---|
| 1 | select * from index_demo where c1 > 1 and c1 < 15 开启一致性视图 |
|
| 2 | insert into index_demo c1 = 13 | |
| 3 | commit | |
| 4 | select * from index_demo where c1 > 1 and c1 < 15 复用序号1的一致性视图 |
聚簇索引间隙锁
在可重复度的隔离级别下,虽然MVCC可以在一致性读的场景下解决幻读问题,但是对于当前读还是会产生幻读问题。例如:如果事务A在查询的时候使用的是当前读(只要事务提交即可见),并且没有机制阻塞事务B的插入的话,那么事务A在序号4的时候就会读到c1=13的记录
| 序号 | 事务A | 事务B |
|---|---|---|
| 1 | select * from index_demo where c1 > 1 and c1 < 15 for update | |
| 2 | insert into index_demo c1 = 13 | |
| 3 | commit | |
| 4 | select * from index_demo where c1 > 1 and c1 < 15 for update |
间隙锁不仅会对扫描过的记录上锁,同时也会对扫描过的间隙上锁,例如上例中的查询语句还会为扫描过的间隙生成间隙锁,所以最终锁的范围是(1,15]。那么上例中事务A在序号1的时候已经生成了间隙锁后事务B的插入语句是会被阻塞的,所以事务B必须等待事务A提交释放锁后才可以进行插入
二级索引间隙锁
二级索引的间隙锁会先为二级索引被扫描过的记录及其间隙上锁,并且会到聚簇索引中将对应的记录上锁。例如上述表中 update index_demo set c3 = ‘test’ where c2 >= 4 and c2 < 9,最终二级索引的锁范围是[4,9],同时会对满足条件的c1=1,3,4聚簇索引上锁
无索引间隙锁
如果没有索引的话锁的范围是(-无穷,+无穷),所以可以理解为整个表都上锁了
insert into select 间隙锁
在可重复读隔离级别下,binlog_format=statement 时执行insert into select from t语句的时候会对select的表上间隙锁。例如:在序号1的时候如果使用的是一致性读,没有对index_demo表上锁的话,事务B是可以执行序号2和3的操作的,那么此时binlog会先记录事务B的插入语句,当事务A在序号4的时候提交会再生成对应的binlog。如果此时要用这份binlog进行数据恢复的话,就会先插入(22,23, ‘t’)到index_demo中,导致index_demo2中的记录错误
| 序号 | 事务A | 事务B |
|---|---|---|
| 1 | insert into index_demo_2 select * from index_demo | |
| 2 | insert into index_demo (c1, c2, c3 ) values (22, 23, ‘t’) | |
| 3 | 提交binlog并commit | |
| 4 | 提交binlog并commit |
