MySQL - 锁

MySQL的锁

读锁和写锁

读锁还可以称为共享锁 ,写锁还可以称为排他锁

读锁和写锁是系统层面上的锁,也是最基础的锁。

读锁和写锁加锁关系如下:

读锁 写锁
读锁 共存 互斥
写锁 互斥 互斥

一个请求占用了读锁,其他请求也可以过来加读锁,但是不能加写锁。这种情况下会出现一个问题,如果一直有请求过来加读锁,那么来了一个加写锁的请求,会一直因为有读锁的存在而阻塞。这样写锁就会被饿死了,为了避免这种情况的发生,数据库做了优化,当有写锁阻塞时,后面的读锁也会阻塞,这样就避免了写锁饿死的现象。

对于 InnoDB 引擎而言,采用的是B+树索引,假设需要将整个表锁住 ,那么需要在整个B+树的每个节点上都加锁,这种方式显然是很低效的。

因此MySQL提出了意向锁的概念,如果要在一个节点上加锁,就必须要在其所有的祖先节点上加意向锁。

表锁和行锁

表锁和行锁是两种不同粒度的锁,除了这两种锁还有一个更大粒度的锁:全局锁

  • 全局锁

    • 全局锁会锁住整个数据库;

    • MySQL使用 flush tables with read lock 命令来加全局锁,使用 unlock tables 解锁;

    • 当加上全局锁以后,除了当前线程以外,其他线程的更新操作都会被阻塞,包括增删改数据表中的数据、建表、修改表结构等;

    • 全局锁的典型使用场景是全库的逻辑备份。

  • 表锁

    • 表锁会锁住一张表;
    • MySQL 使用 lock tables read/write 命令给表加上读锁或写锁,通过 unlock tables 命令释放表锁
      • 通过 lock tables t read 给表 t 加上读锁后,当前线程只能访问表 t,不能访问数据库中的其他表,对表 t 也只有权限,不能进行修改操作。
      • 通过 lock tables t write 给表 t 加上写锁后,当前线程只能访问表 t,不能访问数据库中的其他表,对表 t 有读写权限。
  • 行锁

    • 行锁会锁锁住表中的某一行或者多行

    • MySQL 使用 lock in share mode 命令给行加读锁,用 for update 命令给行加写锁,行锁不需要显示释放,当事务被提交时,该事务中加的行锁就会被释放。

      通过 select k from t where k = 1 for update 命令可以锁住 k 为 1 的所有行

    • 当使用 update 命令更新表数据时,会自动给命中的行加上行锁

    • MySQL 加行锁时并不是一次性把所有的行都加上锁,执行一个 update 命令之后,server 层将命令发送给 InnoDB 引擎,InnoDB 引擎找到第一条满足条件的数据,并加锁后返回给 server 层,server 层更新这条数据然后传给 InnoDB 引擎。完成这条数据的更新后,server 层再取下一条数据

事务A 事务B 事务C 说明
begin 事务A开启事务
select * from t where id = 3 for update; 事务A通过 for update将id=3的行锁住
update t set c = 0 where id = c; 事务B执行update命令(将所有的c都更新为0)(执行到id=3的行时会阻塞)
set session transaction isolation level READ UNCOMMITTED; 开启事务 C,并且将事务 C 的隔离级别修改为未提交读;
select * from t; 事务C查询表信息,发现前两行的c都被更新为0,但是id=3的c并没有被事务B修改为0,说明事务B被阻塞了
commit 提交事务A(事务A加的锁释放了,事务B可以继续加锁了,继续更新)
select * from t; 查询表的信息,发现都事务B已经更新完成

MySQL - 锁
(可以看到事务B执行到id=3的行就被阻塞了,后面的数据都没有更新)

MySQL - 锁
(太久没操作搞超时了,,,又重新按流程来了一遍,通过观察事务C的查询结果,可以发现事务A提交后事务B才继续执行了)

MySQL - 锁

上一篇:测试面试题合集4(数据库)


下一篇:Sql错题合集(1)