Skip to content

在 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;
session1session2
T1beginbegin
T2select * from tt where id = 1 lock in share mode;
T3select * from tt where id = 1;
T4update tt set a = 1 where id = 1; 阻塞
T5commit
T6阻塞恢复

INFO

在 InnoDB 的事务中,行锁是需要的时候才加,是通过索引上的索引项加锁来实现的,并且是等到事务结束之后才释放

2)对记录加写锁,其余事务不能够加读锁或者写锁。

sql
SELECT ... FOR UPDATE;
session1session2
T1beginbegin
T2select * from tt where id = 1 for update;
T3update tt set a = 1 where id = 1;
T4commit
T5update 正常执行

这里需要区分一下读取的两种方式:

  • 快照读:当通过 MVCC 生成的快照读取数据时,不会出现幻读的问题
  • 当前读,读取所有已经提交的记录的最新值,加锁的 SELECT 或者对数据进行增删改的时候都会进行当前读,这种情况是会出现幻读的问题

MySQL 之中有提供了如下几种行锁:

记录锁,仅仅把一条记录锁住,它也是分为读锁和写锁之分的。

在 MySQL 45 讲中,提到了,加锁的规则:

  • 原则:加锁的基本单位是 Next-Key Lock
  • 原则:查找过程之中,访问到的对象才会进行加锁,如果进行了索引覆盖,则会锁二级索引,而不是锁主键
  • 等值查询时,给唯一索引加锁的时候,next-key lock 退化为行锁,如果不是唯一索引,则会向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
  • 优化 :索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
  • 范围查询:无论是否是唯一索引,范围查询都需要访问到不满足条件的第一个值为止