mysql 全局锁、表级锁、行级锁

本文详细介绍了MySQL中的锁机制,包括全局锁、表级锁和行级锁的使用及原理。在备份数据库时,全局锁能确保数据一致性,但可能导致业务暂停;表级锁通过lock tables命令控制并发,而行级锁则降低了锁的粒度,提高了并发性能。MySQL的可重复读隔离级别利用MVCC保证事务一致性。在事务中合理安排SQL语句顺序,可以减少锁等待,提升并发处理能力。
摘要由CSDN通过智能技术生成

一.什么是锁

锁,其实就是计算机在执行多线程或线程时用于并发访问同一共享资源时的一种同步机制,MySQL中的锁是在服务器层或存储引擎层实现的,保证了数据访问的一致性与有效性。

二、全局锁、表级锁、行级锁

1.全局锁

全局锁就是对整个数据库实例加锁,MySQL提供了一个加全局读锁的方法,命令是flush tables with read lock(FTWRL)。

当你需要将整个库处于只读状态(不能写入)的时候,可以使用这个命令,之后线程的以下语句就会被阻塞:数据更新语句(数据的增删改),数据定义语句(建表、修改表结构等)等。

加锁

flush tables with read lock;

解锁

unlock tables;

首先来看这样一种场景,需要给数据库做逻辑备份。

假设在备份的期间,有一个用户,他购买了“实战课程”,业务逻辑就要扣除他的余额,并在已购课程里添加这门课程。

如果时间顺序上是先备份账户余额表(user_accout),再用户购买课程,然后备份用户课程表(user_accout),会出现什么情况 ?看下图:

上图可以看出最终的备份情况,用户的账户余额没扣,但多了一门购买课程。如果此时的这个备份用作数据的恢复,那么用户会发现自己购买的时候没花钱却拥有一门购买课程。

或者说如果备份的表顺序互换,那么用户会发现自己购买的时候余额扣了但未拥有这门购买课程。总之,这两情况发生都是不对的。

我们可以在备份期间给数据库加上全局锁,那么用户的购买操作就会被阻塞,就不会出现上面的两种问题了。全局锁的典型使用场景是做全库逻辑备份,也就是把整个库的每个表都查出来存成文本。

 

Mysql中数据备份使用的命令是mysqldump命令。将数据库中的数据备份成一个文本文件。表的结构和表中的数据将存储在生成的文本文件中。

mysqldump命令的工作原理很简单,其实就是,先查出需要备份的表的结构,在文本文件中生成一个CREATE语句,用于备份表的创建。然后,将源库表中的所有记录转换成一条INSERT语句,用于备份表的数据插入。

 

官方自带的mysqldump,当使用参数-single-transaction的时候,导出数据之前就会启动一个事务,来确保拿到一致性视图,而由于MVCC的支持,这个过程中数据是可以正常更新的,因为读取的数据在更新前已确认。

但前提是数据库的引擎要支持事务,如果不支持的话,就要使用上面我们提到的flush tables with read lock(FTWRL )命令了。

 

除此之外,set global readonly = true 也可以让全库进入只读状态(不能写入),但还是建议适用 FTWRL 命令方式,主要有以下两个原因:

1) 在有些系统中,readonly的值会被用来做其他逻辑处理,也就是说,该全局变量有多个用途,比如用来判断一个库是主库还是备库。因此修改 global 变量的方式影响面更大,不建议适用。

2) 在异常处理机制上有差异,如执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。

而将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。

业务的更新不只是增删改数据(DML),还有可能是加字段等修改表结构的操作(DDL)。不论是哪种方法,一个库被全局锁上之后,你要对里面任何一个表做加字段操作,都会被锁住的。

即使没有被全局锁住,加字段也不是就能一帆风顺的,因为你还会碰到表级锁。

 

全局锁带来的问题:虽然保证了备份数据的一致性、准确性,但是整个库处于只读状态,这就意味着业务的暂停。(业务数据->主库->从库)

如果你在主库上备份,那么备份期间主库不能执行更新操作,那么业务基本就得暂停。(全局锁锁主库)

如果你在从库上备份,那么备份期间从库不能执行主库同步过来的 binlog(增删改部分),会导致主从延迟。(全局锁锁从库)

 

表级锁

MySQL中表级别的锁有两种:一种是表锁,另一种是元数据锁(meta data lock,MDL)。

表锁的语法是 lock tables .. read/write,比如lock tables t1 read,t2 write。与 FTWRL命令类似,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。

在还没有出现更细粒度的锁的时候,表锁是最常用的处理并发问题的方式,但对于 InnoDB 这种支持行锁的引擎,一般不使用 lock tables 命令来控制并发,毕竟,锁住整个表的影响面还是很大的。因此,某些引擎不支持行锁,需要通过表锁来控制并发。支持行锁的引擎,就不建议使用表锁了。

如何加表锁?

lock tables t1 read,t2 write;

这个语句有两个含义:

  1. 对其他线程来说,t1表只能读;t2表,不可读不可写
  2. 对本线程来说,t1表只能读;t2表可读可写

另一种表级的锁是 MDL,元数据锁主要是面向DML和DDL之间的并发控制,它不需要我们显式的加,系统默认会在访问一个表的时候加上。

因此在 MySQL 5.5 之后加入了MDL,当对一个表做增删改查操作(DML+DQL语句)的时候,加 MDL 读锁。当对表做结构变更操作(DDL语句)的时候,加 MDL 写锁。

 

MySQL可重复读隔离级别的实现原理

MySQL默认的隔离级别是可重复读,即:事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。 那么MySQL可重复读是如何实现的呢?

使用的的一种叫MVCC的控制方式 ,即Mutil-Version Concurrency Control,多版本并发控制,类似于乐观锁的一种实现方式。

实现方式:

InnoDB在每行记录后面保存两个隐藏的列,分别保存了这个行的创建时间(版本号)和行的删除时间(版本号)。这里存储的并不是实际的时间值,而是系统版本号,当数据被修改时,版本号加1。

在读取事务开始时,系统会给当前读事务一个版本号n,事务会读取版本号<=该版本号n的数据。

此时如果其他写事务修改了这条数据,那么这条数据的版本号就会加1,从而比当前读事务的版本号高,但当前读事务读的是该版本号n内的数据。

ps:可重复读隔离级别的事务启动的时候会创建一个视图read-view,这个视图是不会改变的,之后事务执行期间,即使有其他事务修改了数据,该事务看到的数据仍然跟启动时看到的一样。也就是说,一个在可重复读隔离级别下执行的事务,好像与世无争,不受外界影响。

行锁

行级锁是MySQL中粒度最小的一种锁。MYISAM引擎只支持表级锁,而INNODB引擎能够支持行级锁,所以行锁都是出现在InnoDB事务中。行锁是针对数据表中数据行的锁。比如事务A更新了一行,还没提交,这个时候事务B也要更新同一行,则必须等待事务A操作完成之后才能进行更新。

两阶段锁协议

事务A持有的两个记录的行锁都是在commit的时候才释放的,事务B的update语句会被阻塞,直到事务A执行commit之后,事务B才能继续执行(成功update)。

在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。

假设要实现一个电影票在线交易业务,顾客A要在影院B购买电影票。业务需要涉及以下sql操作:

1.从顾客A账户余额中扣除电影票价

2.给影院B的账户余额增加这张电影票价

3.记录一条交易日志

为了保证交易的原子性,要把这三个操作放在一个事务中。如何安排这三个语句在事务中的顺序呢?

如果同时有另外一个顾客C要在影院B买票,那么这两个事务冲突的部分就是语句2了。因为它们要更新同一个影院账户的余额,即需要修改同一行数据。根据两阶段锁协议,所有的操作中需要的行锁都是在事务提交的时候才释放的。这里,2处需要行锁,所以,如果把语句2安排在最后,比如按照1、3、2这样的顺序,那么影院账户余额这一行的锁时间就会减少。这就减少了事务之间的锁等待,提升了并发度。

mysqldump之--single-transaction参数

这是一个基于InnoDB-RR-MVCC的操作。在数据备份的时候,开启一个事务,该事务的整个过程中,是可重复读的,即读取的数据是已确认版本下的,而不是其它事务进行写操作后更新的版本下的数据。

此参数选项会将隔离级别设置为:REPEATABLE READ。并且随后再执行一条START TRANSACTION语句,让整个数据在dump的过程中保证数据的一致性,这个选项对InnoDB的数据表很有用,且不会锁表。

但是这个不能保证MyISAM表和MEMORY表的数据一致性,它们必须加--lock-all-tables。mysql数据库常用的引擎有InnoDB,Myisam,Memory。  

使用--single-transaction的注意事项

1)要求innodb引擎

2)不能在执行的同时,有alter table ,drop table,rename table,truncate table的操作

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值