全局锁

即对整个数据库实例加锁,flush tables with read lock,该命令让整个库处于只读状态,数据库的增删改查和DDL语句都会阻塞

使用场景

在对数据库做逻辑备份的时候,例如:mysqldump做逻辑备份时默认会加全局锁。当我们在执行逻辑备份时加上–single-transaction(只能支持所有表使用事务引擎的库)参数时默认会开启一致性读,而由于MVCC的支持这个过程中数据可以正常更新

优点

  1. 保证数据一致性。例如:用户购买商品,先保存订单表,然后扣减余额。在用户未购买商品时开始进行逻辑备份(如果不上锁),此时已经备份好了订单表,还未备份余额表,用户完成购买操作(订单表增加记录、余额表扣减余额),此时再对余额表进行备份,就会导致余额和订单信息不一致问题

    缺点

  2. 阻塞业务操作。如果在主库上执行,那么主库无法正常写入

  3. 主从延迟。在从库上执行,导致从库无法同步主库上传入的binlog

    表级锁

    表锁

    lock tables … read/write,也可以用unlock tables主动释放锁。在限制其他线程的读写同时,也限制了本线程接下来的操作

    元数据锁(MDL)

    MDL不需要显示使用,在访问一个表的时候会被自动加上。
    对一个表增删改查的时候会上MDL读锁,在对表结构进行变更(变更表结构或加索引)时会上MDL写锁。读-读不互斥,读-写互斥

    行锁

    MySQL的行锁是通过各个引擎自己实现的。为了防止并发修改数据导致脏读,所以当我们在执行锁定读的语句时会进行上锁,并且直到事务提交才释放锁。锁定读语句:select … lock in share mode,select … for update,update…,delete…
    例如下表
    1. create table index_demo(
    2. c1 int,
    3. c2 int,
    4. c3 char(1),
    5. primary key (c1),
    6. index idx_c2(c2)
    7. )
    | c1 | c2 | c3 | | :—-: | :—-: | :—-: | | 1 | 4 | u | | 3 | 9 | d | | 4 | 4 | a | | 5 | 3 | y | | 9 | 11 | u | | 15 | 12 | z |

RU&RC

在读未提交和读提交的隔离级别下,会为索引扫描过且满足条件的记录上锁
例如:针对上表进行update index_demo set c3 = ‘test’ where c1 > 1 and c1 < 15 and c2 > 3

  1. 根据聚簇索引(c1)找出第一条c1 > 1 and c1 < 15的记录(3,9,d),并对该记录上锁
  2. 判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
  3. 满足,不释放锁,返回给server层,寻找下一条(4,4,a)并对该记录上锁
  4. 判断是否满足c1 > 1 and c1 < 15 并对该记录上锁
  5. 满足,判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
  6. 满足,不释放锁,返回给server层,寻找下一条(5,3,y)并对该记录上锁
  7. 判断是否满足c1 > 1 and c1 < 15
  8. 满足,判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
  9. 不满足,释放该记录的锁,寻找下一条(9,11,u)并对该记录上锁
  10. 判断是否满足c1 > 1 and c1 < 15
  11. 满足,判断是否满足c1 > 1 and c1 < 15 and c2 > 3
  12. 满足,不释放锁,返回给server层,寻找下一条(15,12,z)并对该记录上锁
  13. 判断是否满足c1 > 1 and c1 < 15
  14. 不满足,释放该记录的锁,返回给server层 “查询完毕”

RR&Serializable

在可重复度和串行化的隔离级别下,会对索引扫描过的记录上锁并且为扫描过的间隙上锁
例如:针对上表进行update index_demo set c3 = ‘test’ where c1 > 1 and c1 < 15 and c2 > 3

  1. 根据聚簇索引(c1)找出第一条c1 > 1 and c1 < 15的记录(3,9,d),并对该记录上锁
  2. 判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
  3. 满足,不释放锁,返回给server层,寻找下一条(4,4,a)并对该记录上锁
  4. 判断是否满足c1 > 1 and c1 < 15 并对该记录上锁
  5. 满足,判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
  6. 满足,不释放锁,返回给server层,寻找下一条(5,3,y)并对该记录上锁
  7. 判断是否满足c1 > 1 and c1 < 15
  8. 满足,判断是否满足查询条件 c1 > 1 and c1 < 15 and c2 > 3
  9. 不满足,不释放该记录的锁,寻找下一条(9,11,u)并对该记录上锁
  10. 判断是否满足c1 > 1 and c1 < 15
  11. 满足,判断是否满足c1 > 1 and c1 < 15 and c2 > 3
  12. 满足,不释放锁,返回给server层,寻找下一条(15,12,z)并对该记录上锁
  13. 判断是否满足c1 > 1 and c1 < 15
  14. 不满足,不释放该记录的锁,返回给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提交释放锁后才可以进行插入
mysql-间隙锁.png

二级索引间隙锁

二级索引的间隙锁会先为二级索引被扫描过的记录及其间隙上锁,并且会到聚簇索引中将对应的记录上锁。例如上述表中 update index_demo set c3 = ‘test’ where c2 >= 4 and c2 < 9,最终二级索引的锁范围是[4,9],同时会对满足条件的c1=1,3,4聚簇索引上锁

mysql-二级索引间隙锁.png

无索引间隙锁

如果没有索引的话锁的范围是(-无穷,+无穷),所以可以理解为整个表都上锁了

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