mysql有哪些锁?

1、在 MySQL 里,根据加锁的范围,可以分为全局锁、表级锁和行锁三类

2、全局锁

 

2.1、全局锁命令

要使用全局锁,则要执行这条命令:

flush tables with read lock

如果要释放全局锁,则要执行这条命令:

unlock tables

 

当会话断开了,全局锁会被自动释放。

2.2、全局锁应用场景是什么?

 全库逻辑备份

 

举个例子(如上图)

在全库逻辑备份期间,假设不加全局锁的场景,看看会出现什么意外的情况??

如果在全库逻辑备份期间,有用户购买了一件商品,一般购买商品的业务逻辑是会涉及到多张数据库表的更新,

比如在用户表更新该用户的余额,然后在商品表更新被购买的商品的库存。

那么,有可能出现这样的顺序:

1. 先备份了用户表的数据;

2. 然后有用户发起了购买商品的操作;

3. 接着再备份商品表的数据。

也就是在备份用户表和商品表之间,有用户购买了商品。

这种情况下,备份的结果是用户表中该用户的余额并没有扣除,反而商品表中该商品的库存被减少 了,

如果后面用这个备份文件恢复数据库数据的话,用户钱没少,而库存少了,等于用户白嫖了一 件商品。

所以,在全库逻辑备份期间,加上全局锁,就不会出现上面这种情况了

2.3、加全局锁又会带来什么缺点呢?

 

加上全局锁,意味着整个数据库都是只读状态。

那么如果数据库里有很多数据,备份就会花费很多的时间,关键是备份期间,业务只能读数据,而不能更新数据,这样会造成业务停滞。

既然备份数据库数据的时候,使用全局锁会影响业务,那有什么其他方式可以避免?

解决办法:

数据库的引擎支持的事务支持可重复读的隔离级别,那么在备份数据库之前先开启事务,会先创建 Read View

然后整个事务执行期间都在用这个 Read View,而且由于 MVCC 的支持,备份期间业务依然可以对数据进行更新操作。

因为在可重复读的隔离级别下,即使其他事务更新了表的数据,也不会影响备份数据库时的 Read View,

这就是事务四大特性中的隔离性,这样备份期间备份的数据一直是在开启事务时的数据。

备份数据库的工具是 mysqldump,在使用 mysqldump 时加上 –single-transaction 参数的时候, 就会在备份数据库之前先开启事务。

这种方法只适用于支持「可重复读隔离级别的事务」的存储引 擎。

InnoDB 存储引擎默认的事务隔离级别正是可重复读,因此可以采用这种方式来备份数据库。

但是,对于 MyISAM 这种不支持事务的引擎,在备份数据库时就要使用全局锁的方法。

3、表级锁

 3.1、表锁

3.1.1、表锁命令

 举个例子:

如果我们想对学生表(t_student)加表锁,可以使用下面的命令:

//表共享读锁;
//允许当前会话读取被锁定的表,但阻止其他会话对这些表进行写操作。
lock tables t_student read;
//表独占写锁;
//允许当前会话对表进行读写操作,但阻止其他会话对这些表进行任何操作(读或写)。
lock tables t_stuent write;
//释放锁
unlock tables

 

读锁不会阻塞其他客户端读,但是会阻塞写。写锁会阻塞其他客户端读和写。

3.2、元数据锁(MDL)

  • 对一张表进行 CRUD 操作时,加的是 MDL 读锁;
  • 对一张表做结构变更操作的时候,加的是 MDL 写锁;

 

MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更

当有线程在执行 select 语句( 加 MDL 读锁)的期间,如果有其他线程要更改该表的结构( 申请 MDL 写锁),那么将会被阻塞,直到执行完 select 语句( 释放 MDL 读锁)。

反之,当有线程对表结构进行变更( 加 MDL 写锁)的期间,如果有其他线程执行了 CRUD 操作( 申请 MDL 读锁),那么就会被阻塞,直到表结构变更完成( 释放 MDL 写锁)。

不需要显示调用,那它是在什么时候释放的?

MDL 是在事务提交后才会释放,这意味着事务执行期间,MDL 是一直持有的。 那如果数据库有一个长事务(所谓的长事务,就是开启了事务,但是一直还没提交),

那在对表结构做变更操作的时候,可能会发生意想不到的事情,

比如下面这个顺序的场景:

1. 首先,线程 A 先启用了事务(但是一直不提交),然后执行一条 select 语句,此时就先对该表加 上 MDL 读锁;

2. 然后,线程 B 也执行了同样的 select 语句,此时并不会阻塞,因为「读读」并不冲突;

3. 接着,线程 C 修改了表字段,此时由于线程 A 的事务并没有提交,也就是 MDL 读锁还在占用着,

这时线程 C 就无法申请到 MDL 写锁,就会被阻塞, 那么在线程 C 阻塞后,后续有对该表的 select 语句,就都会被阻塞,

如果此时有大量该表的 select 语句的请求到来,就会有大量的线程被阻塞住,这时数据库的线程很快就会爆满了。

为什么线程 C 因为申请不到 MDL 写锁,而导致后续的申请读锁的查询操作也会被阻塞?

这是因为申请 MDL 锁的操作会形成一个队列,队列中写锁获取优先级高于读锁,一旦出现 MDL 写 锁等待,会阻塞后续该表的所有 CRUD 操作。

所以为了能安全的对表结构进行变更,在对表结构变更前,先要看看数据库中的长事务,是否有事务已经对表加上了 MDL 读锁,如果可以考虑 kill 掉这个长事务,然后再做表结构的变更。

 3.3、意向锁

 

在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」;

在使用 InnoDB 引擎的表里对某些纪录加上「排他锁」之前,需要先在表级别加上一个「意向排他锁」;

 

也就是,当执行插入、更新、删除操作,需要先对表加上「意向排他锁」,然后对该记录加排他锁。

而普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的。

不过,select 也是可以对记录加共享锁和独占锁的,具体方式如下:

意向共享锁和意向排他锁是表级锁,不会和行级的共享锁和排他锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享锁(lock tables ... read)和排他锁(lock tables ... write)发生冲突。

表锁和行锁是满足读读共享、读写互斥、写写互斥的。

如果没有「意向锁」,那么加「排他锁」时,就需要遍历表里所有记录,查看是否有记录存在排他锁,这样效率会很慢。

那么有了「意向锁」,由于在对记录加排他锁前,先会加上表级别的意向排他锁,那么在加「排他锁」时,直接查该表是否有意向排他锁,

如果有就意味着表里已经有记录被加了排他锁,这样就 不用去遍历表里的记录。

所以,意向锁的目的是为了快速判断表里是否有记录被加锁

 

4、行级锁

InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。

 

前面也提到,普通的 select 语句是不会对记录加锁的,因为它属于快照读。如果要在查询时对记录加行锁,可以使用下面这两个方式,这种查询会加锁的语句称为锁定读。

上面这两条语句必须在一个事务中,因为当事务提交了,锁就会被释放,所以在使用这两条语句的时候,要加上 begin、start transaction 或者 set autocommit = 0

共享锁(S锁)满足读读共享,读写互斥。排他锁(X锁)满足写写互斥、读写互斥。

行级锁的类型主要有三类:

  • Record Lock,行锁,也就是仅仅把一条记录锁上;
  • Gap Lock,间隙锁,锁定一个范围,但是不包含记录本身;
  • Next-Key Lock:Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。

 

4.1、行锁

  • 当一个事务对一条记录加了 S 型行锁后,其他事务也可以继续对该记录加 S 型行锁(S 型与 S 锁兼容),但是不可以对该记录加 X 型行锁(S 型与 X 锁不兼容);
  • 当一个事务对一条记录加了 X 型行锁后,其他事务既不可以对该记录加 S 型行锁(S 型与 X 锁不兼容),也不可以对该记录加 X 型行锁(X 型与 X 锁不兼容)。

4.1.1、行锁出现的类型情况

 

 

 

4.2、间隙锁

 

间隙锁只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。

 

 

4.3、临建锁

 

posted @ 2025-04-24 18:17  XiMeeZhh  阅读(98)  评论(0)    收藏  举报