锁
在 MySQL 之中,按照锁的粒度不同,分为一下三类:
- 全局锁:锁定数据库中的所有表
- 表级锁:每次操作锁住整张表
- 行级锁:每次操作锁住对应的行数据
全局锁
全局锁就是对整个数据库实例加锁,加锁之后整个实例就处于只读状态,已经更新操作的事务提交语句都将会被阻塞。
其典型的使用场景就是对全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,从而保证数据的完整性。
客户端连接断开,会自动释放全局锁
sql
-- 加锁
flush tables with read lock;
-- 解锁
unlock tables;
表级锁
MySQL 中的表锁有两种:一种是表锁,一种是元数据锁(MDL)
表锁的语法如下:
sql
-- 读锁,其他客户端能够进行读,但是写操作会被阻塞
lock table ... read
-- 写锁,其他客户端的读和写都是处于阻塞状态
lock tables 表名 write;
-- 释放锁
unlock tables
另一种锁是 MDL ,不需要显式使用,在访问某个表的时候自动加上,主要是为了防止进行 select 等操作的时候,有客户端修改了表结构
当一个表进行正常的增删改查操作的时候,加 MDL 读锁,而对表结构修改的时候,需要加 MDL 写锁。读读不互斥,读写是互斥的
INFO
****行级锁
对于数据库的某行记录,无非就两种操作,读 和 写。
- 对于写,同一时间只能够由一个客户端进行修改,为此只能够通过加锁的方式进行解决
- 对于读,在事务篇,在已提交读或者可串行化这种隔离级别之下,可以使用 MVCC 来进行解决,在串行化中,通过加锁来解决
在读记录时,除了使用 MVCC 外,还可以直接通过锁来解决
1)对记录加读锁,其余事务仍然能够获取读锁,但是不能够获取写锁
sql
SELECT ... LOCK IN SHARE MODE;
session1 | session2 | |
---|---|---|
T1 | begin | begin |
T2 | select * from tt where id = 1 lock in share mode; | |
T3 | select * from tt where id = 1; | |
T4 | update tt set a = 1 where id = 1; 阻塞 | |
T5 | commit | |
T6 | 阻塞恢复 |
INFO
在 InnoDB 的事务中,行锁是需要的时候才加,是通过索引上的索引项加锁来实现的,并且是等到事务结束之后才释放
2)对记录加写锁,其余事务不能够加读锁或者写锁。
sql
SELECT ... FOR UPDATE;
session1 | session2 | |
---|---|---|
T1 | begin | begin |
T2 | select * from tt where id = 1 for update; | |
T3 | update tt set a = 1 where id = 1; | |
T4 | commit | |
T5 | update 正常执行 |
这里需要区分一下读取的两种方式:
- 快照读:当通过 MVCC 生成的快照读取数据时,不会出现幻读的问题
- 当前读,读取所有已经提交的记录的最新值,加锁的 SELECT 或者对数据进行增删改的时候都会进行当前读,这种情况是会出现幻读的问题
MySQL 之中有提供了如下几种行锁:
记录锁,仅仅把一条记录锁住,它也是分为读锁和写锁之分的。
在 MySQL 45 讲中,提到了,加锁的规则:
- 原则:加锁的基本单位是 Next-Key Lock
- 原则:查找过程之中,访问到的对象才会进行加锁,如果进行了索引覆盖,则会锁二级索引,而不是锁主键
- 等值查询时,给唯一索引加锁的时候,next-key lock 退化为行锁,如果不是唯一索引,则会向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
- 优化 :索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
- 范围查询:无论是否是唯一索引,范围查询都需要访问到不满足条件的第一个值为止