欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MySQL锁,你真的理解么?

程序员文章站 2022-04-09 23:12:27
...

MySQL锁

1、基本概念

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中的数据也是一种供多个用户使用的共享资源,

当多个用户并发地存取数据时,在数据库加锁的目的可以保证数据库数据的一致性。

  • 锁的类型

在数据库中,对数据的操作其实只有两种,即读和写,所以可用共享锁和互斥锁实现,即共享锁间之间是兼容的,而互斥锁间不兼容。

  • 共享锁(读锁):允许事务读
  • 互斥锁(写锁):允许事务删除或者更新一行数据

共享锁代表了读操作、互斥锁代表了写操作,所以我们可以在数据库中并行读,但是只能串行写,只有这样才能保证不会发生线程竞争,实现线程安全。

  • 锁的粒度

我们按照锁的粒度可以分为全局锁、表锁和行锁,不同的存储引擎拥有的锁粒度是不同的 MySQL锁,你真的理解么?

在下面的文章中,我们按照锁粒度的划分分别学习全局锁、表级锁和行锁

2、全局锁

2.1基本概念

从名称上就可以看出,全局锁是对整个数据库实例加锁。MySQL提供了一个加全局读锁的方法,即Flush tables with read lock (FTWRL)。当对数据库使用这个命令时,数据库将会处于只读状态,之后的其它线程的语句:如数据更新语句(数据的增删改)DML,数据的定义语句(表结构的修改)DDL均会被阻塞。

2.2使用场景

全局锁的典型使用场景是做数据库逻辑备份,也就是将整个库中的所有表都select出来存成文本。在备份过程中的整个库要处于只读状态,这会存在以下问题

  • 如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就能停止
  • 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。

如果选用的引擎不支持事务(如MyISAM),在对数据库做逻辑备份时只能选择FTWRL。如果引擎支持事务,并且支持可重复读的隔离级别(如InnoDB),我们就可以用一致性视图来做备份,MySQL自带的逻辑备份工具是mysqldump。当mysqldump使用参数-single-transaction时,导入数据之前就可以启动一个事务,来确保拿到一致性视图。在MVCC的支持下,导入数据的过程中是可以正常更新的。

2.3lock/unlock命令

  • 全局锁加锁命令
flush tables with read lock
  • 释放全局锁的命令
unlock tables

断开连接也能释放全局锁

3、表级锁

MySQL的表级锁有两种:表锁,元数据锁(meta data lock MDL),

3.1表锁

表锁的语法是:lock tables ... read/write,可以利用unlock tables主动释放锁,也可以在连接断开时自动释放。需要注意的是,lock tables除了限制其它线程的读写,也会限制自身接下来的操作对象

举例

如果在某个线程 A 中执行 lock tables t1 read, t2 write这个语句,则其他线程写 t1、读写 t2 的语句都会被阻塞。与此同时,线程 A 在执行 unlock tables 之前,也只能执行读 t1、读写 t2 的操作。连写 t1 都不允许,自然也不能访问其他表。

这种操作整个表的影响很大,所以对于使用InnoDB引擎下,一般不适用lock tables命令控制并发。

3.2MDL

MDL全称是metadata lock,即元数据锁,它的作用主要是为了维护表中数据的一致性,即用于解决DDL操作与DML操作的一致性。

  • 解决的问题

其主要解决了2个问题,一个是事务隔离问题,比如在可重复隔离级别下,会话A在2次查询期间,会话B对表结构做了修改,两次查询结果就会不一致,无法满足可重复读的要求;另外一个是数据复制的问题,比如会话A执行了多条更新语句期间,另外一个会话B做了表结构变更并且先提交,就会导致slave在重做时,先重做alter,再重做update时就会出现复制错误的现象。

元数据锁是server层的锁,表级锁,每执行一条DML、DDL语句时都会申请MDL锁,DML操作需要MDL读锁,DDL操作需要MDL写锁(MDL加锁过程是系统自动控制,无法直接干预,读读共享,读写互斥,写写互斥),申请MDL锁的操作会形成一个队列,队列中写锁获取优先级高于读锁。

NOTE:一旦出现写锁等待,不但当前操作会被阻塞,同时还会阻塞后续该表的所有操作(读操作当然也会被阻塞)。事务一旦申请到MDL锁后,直到事务执行完才会将锁释放。

MDL锁一旦发生可能会对数据库的业务带来其它的影响,因为后续对该表的访问都会被阻塞,造成连接积压。那么我们该如何避免MDL锁的发生呢?

  • MDL锁的优化
  • 避免长事务
  • 在DDL操作中设定等待时间

4、行锁

行锁每次锁定的是一行数据,行级锁定不是MySQL自己实现锁定的方式,是由存储引擎实现的(InnoDB)自己实现的。

InnoDB实现了以下两种类型的行锁

  • 共享锁(读锁)

允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。若事务T对数据对象A加上共享锁,则事务T可以读A但不能修改A,而其他事务只能再对对象A加共享锁,而不能加排他锁,直到事务T释放A上的共享锁。这保证了其他事务可以读A,但在事务T释放A上的S锁之前不能对A做任何修改。

  • 排他锁(写锁)

允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。若事务T对数据对象A加上排他锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁

NOTE:排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。

为了使得表锁和行锁共存,实现多粒度锁机制,InnoDB存在了两种内部使用的的意向锁

  • 意向共享锁

事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的意向共享锁。

  • 意向排他锁

事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的意向排他锁

NOTE:意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁。

  • 死锁

当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁

案例

事务A 事务B
begin;update t set k=k+1 where id=1; begin
update t set k=k+1 where id=2;
update t set k=k+1 where id=2;
update t set k=k+1 where id=1;

事务 A 在等待事务 B 释放 id=2 的行锁,而事务 B 在等待事务 A 释放 id=1 的行锁。 事务 A 和事务 B 在互相等待对方的资源释放,就是进入了死锁状态。

解除死锁的策略

  • 设置超时时间参数innodb_lock_wait_timeout,
  • 将参数 innodb_deadlock_detect 设置为 on,发起死锁检测,当发现死锁后,主动回滚死锁链条中的某一个事务,让其它事务得以继续执行。
  • 间隙锁(Next-key)

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的 索引项加锁。对于键值在条件范围内但并不存在的记录叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁 (Next-Key锁)。

使用间隙锁的目的是防止幻读

MySQL锁,你真的理解么?

关注公众号:10分钟编程,让我们每天博学一点点

公众号回复success,领取独家整理的学习资源,JAVA、大数据全套视频资料

参考

[1]林晓斌.《MySQL实战45讲》

[2]https://www.cnblogs.com/kunjian/p/11993708.html

[3]http://mysqlpub.com/thread-5383-1-1.html

[4]http://blog.csdn.net/c466254931/article/details/53463596

相关标签: 数据库技术