mysql锁类型

一、Mysql锁的类型
整体上,Mysql锁的类型,从全局的到细节的,包含如下几个:
1.全局锁
2.表级锁: 普通的表锁、MDL (元数据锁)
3.行锁:读锁(共享锁)、写锁(排他锁、叉锁)
4.间隙锁

二、 全局锁

	全局锁就是对整个数据库实例加锁。

Mysql提供了一个加锁的语句:Flush tables with read lock (FTWRL)。它能使整个实例上面,只读,所有的写和更新,都会被阻塞。全局锁的使用经典的使用场景是做全局的数据备份使用,具体的操作,可能平时我们碰到不多,不过要了解几点:
1.每次全局备份过程中,如果是InnoDB引擎完全可以通过MVCC创建一致性视图,来保证不受备份中,其他操作的影响,问题是不一定所有的数据引擎都是InnoDB
2.我们同样可以通过set global readonly=true 来进行只读性的设置,但是和FTWRL的区别如下:
有些数据库把第一种模式用作,设置成备库的只读限制,而不是用来做备份的,影响面比较大;

异常处理机制不一样:FTWRL这种机制,如果设置之后,客户端异常断开连接了,数据库会主动释放全局的锁,恢复正常;而set这种方式,客户端异常断开了,就不会恢复原状,数据库会一直只读,影响很大。

具体对数据库的全局锁,不仅仅阻塞增删改操作(DML),对数据库表的增删改字段(DDL)也会被阻塞

三、表级锁
我们首先要知道的是,每次进行select操作或者DML的时候,对表加的都是MDL的读锁,而进行DDL的时候,对表加的是MDL的写锁,让我们首先来个印象。接下来来看看普通的表锁与MDL(元数据锁meta data lock)的区别。
1、普通的表锁
普通的表锁也是分读锁与写锁,数据库提供语句操作:lock tables … read/write,使用unlock tables进行释放锁。具体注意的点是:加了普通的表锁之后,对当前加锁线程接下来的数据库操作,都是有影响的。
举个例子:如果A线程使用语句lock tables t1 read, t2 write; 这个语句,那么,其他线程写t1和读写t2都会被阻塞;同时线程A再进行unlock之前,也只能读t1和读写t2,连写t1都是不被允许的。自然也不能访问其他的表
在没有出现行锁之前,都是通过表锁进行并发控制的,上面例子可见,影响面还是太大,限制太严格了。
2、元数据锁(MDL)
MDL不需要主动加锁,每当我们访问一个数据表的时候,会自动被加上,作用是防止在我们进行表的操作的时候,进行了表结构的变更。再5.5这个版本中被引入了Mysql中:

  • 当进行一个表的结构变更的时候,加MDL的写锁

  • 当对一个表进行增删改查的时候,加MDL的读锁

    读写锁的MDL之间的互斥关系是:

  • 读锁与读锁不互斥

  • 读锁与写锁互斥

  • 写锁与写锁互斥

具体有个经典的例子:经常发生的是,我们给一个表加了个一个字段或者几个字段,很小心了,但是加的过程中,直接整个表挂了,接下来的操作都失败了或者不返回。接下来我们就看看具体的操作过程:

sessionAsessionBsessionCseeeionD
begin;
select * from t limit1
select * from t limit1
alter table t add f int (block)
select * from t limit1 (block)

*可见,我们sessionC操作之后,由于sessionA是没有结束事务的,我们MDL会随着事务的开启而加锁,事务的结束而释放锁,所以,sessionA这时候保持住了MDL的读锁。然后sessionC想要获取MDL的写的时候,由于读写互斥,sessionC就被阻塞了。接下来的语句,也都执行不了了,因为接下俩的语句要申请MDL的读锁,而有写锁已经在阻塞状态,读锁又要排队等这个写锁执行释放,那接下来的现象可想而知。

我们如何安全的对一个表进行加字段的操作呢:*
1.在information_schema库里面的innodb_tx表中,可以查到当前正在执行的事务,如果是一个长事务,我们可以先考虑kill掉这个事务
2.如果是频繁访问的断事务比较多的情况,我们可以使用alter table tablename wait N add col这种类型的操作,如果拿不到MDL写锁,一段时间会释放阻塞,不长期影响数据库。

四、行锁
这个过程比较复杂,首先,我们来看看,Mysql加行锁,是使用两阶段加锁策略的,我们看看什么叫做两阶段加锁:

	两阶段锁协议,整个事务分为两个阶段,前一个阶段为加锁,后一个阶段为解锁。在加锁阶段,事务只能加锁,也可以操作数据,但不能解锁,直到事务释放第一个锁,就进入解锁阶段,此过程中事务只能解锁,也可以操作数据,不能再加锁。两阶段锁协议使得事务具有较高的并发度,因为解锁不必发生在事务结尾。它的不足是没有解决死锁的问题,因为它在加锁阶段没有顺序要求。如两个事务分别申请了A, B锁,接着又申请对方的锁,此时进入死锁状态。

1、什么时候加行锁
正常,我们select语句时候,是不会添加行锁的,只会加上MDL的读锁,即使这条语句是全表扫描,也不会加行锁,只不过全表扫描,查询较慢罢了,并不会因为锁的问题而对其他操作进行阻塞。下面是我总结的一些加行锁的场景:*
-

  • select * from t where id = 1 in share model 对主键为1的这一行,加行锁,共享锁
  • select * from t where id = 1 for update 对主键为1的这一行,加行锁,排它锁,叉锁
  • update t set col1 = 1 where id =1 对主键为1的这一行加行锁,排它锁,叉锁
  • update t set col1 = 1 where col2 =1 如果col2没有索引,那么是加普通表锁;如果col2是非唯一索引,对所有col2为1的行,加行锁;如果col2是唯一索引,对col2为1的这一行,加行锁。行锁都是排它锁

p.s.:当然上面所有所列取的操作,都是首先加了MDL的读锁的

2、加行锁的影响

	行锁,之所以存在,就是提高并发度的。取代以前,我们要整表进行加锁,而引起同一时刻,只能有一个线程对数据表进行增删改的操作,下面我们看一个具体的数据库操作:
事务A事务B
begin;
update t set k = k+1 where id = 1;
update t set k = k+2 where id = 2;
begin;
update t set k = k+3 where id = 1;
commit;
  • 事务B的update会被阻塞,因为id为1的这行行锁被事务A所持有
  • begin的时候,没有任何行锁被持有,只有当具体操作进行是,依次请求MDL的读锁,这一行的排它行锁
  • 所有当前事务持有的行锁,语句执行完都不会释放,直到commit之后才释放
    所以,按照这种逻辑,越是并发度高的数据表,越要靠事务的后面写,因为持有行锁时间短,影响并发度的时间越短。

五、乐观锁悲观锁

1.乐观锁和悲观锁的概念

	乐观锁是一种思想,实现方法为,表中会有一个版本字段,第一次读取数据的时候会获得这个字段;当处理完我们的逻辑操作后,需要更新数据的时候,会比较这个版本字段是否一致,一致则更新成功,反之则更新失败,每次更新成功后,版本地段默认加1;之所以叫乐观锁,因为每次拿数据的时候,默认数据不会改变,之后在最后进行更新的时候才去判断数据是否更改。
	
	悲观锁顾名思义,和乐观锁比较就是,我的思想悲观,我认为我拿到的数据一定会被别人修改,为了防止被人修改,当我一拿到数据,我就对它进行加锁,这样别人就无法获得我的数据,只有等我操作完成释放锁后,后面的人才能获得我的数据。悲观锁的实现直接在语句后面加for update即可,
	例如:select * from T where id =1 for update

六 、死锁和解决办法

	死锁是指两个或者两个以上的进程在执行过程中,由于竞争资源或者彼此通信而造成的一种阻塞现象,若无外力的作用下,将无法推进下去,此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

例如下面的例子:

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

这就是一个经典的死锁场景,我们来分析下:

  • 事务A的update t set k = k+1 where id = 1;获取了id为1这一行的行锁(排它锁)
  • 事务B的update t set k = k+3 where id = 2;获取了id为2这一行的行锁
  • 事务A的update t set k = k+2 where id = 2;要获取id为2的行锁,而获取不到,阻塞
  • 事务B的update t set k = k+3 where id = 1;要获取id为1的行锁,获取不到,阻塞

对于这种,Mysql有两种机制进行处理:

  • innodb_lock_wait_timeout可以通过这个参数,进行设置锁等待时间,超过这个时间,阻塞的进程释放所有持有的锁,回滚。
  • innodb_deadlock_detect通过设置为on,能主动监测死锁,通过回滚死锁联调中的一个事物,来解决死锁
    第一种情况虽然能控制,死锁,但是时间不好设置,例如我们设置一个10s,如果一个线程被锁住,要等待10s才能进行回滚,并发度自然不高,如果我设置低了,1s,那么一个正常等待的,并非死锁,也会被回滚。如此一来得不偿失。下面重点说说主动死锁检测

七、主动死锁检测
每把innodb_deadlock_detect设置成on,MySQL会主动检测死锁:

  • 一个线程加入

  • 即将被等待其他线程的锁而堵住

  • 判断当前线程持有的锁,是否堵住了其他系统中正在运行的线程

  • 如果是,将回滚当前线程的事务

    看起来很好,然后会有代价:每次对比是否当前线程堵住了其他线程这一步,会对比所有系统正在执行的线程,时间复杂度是O(n)。当前执行的线程数少不成问题,如果是1000个正在执行的线程,那么这就是100w次的对比,这个过程极度消耗CPU资源。结果可能检测出没有死锁,然后会发现:最终CPU飚的老高,然而执行的条数没几个!
    解决办法有下面几个:

  • 临时关掉innodb_deadlock_detect,但是这样会有很多超时,不实用

  • 控制数据库的并发度:可以从中间件这层控制(Java这里),或者有能力的从数据库这一层进行控制

  • 业务字段拆分:例如我们将一行记录拆分成多行,让一行的并发度下降(例如并发扣减id为1这一行的金额,我们可以拆分成id为100,id为200,id为300这三行的金额,最后要查询的时候,将这三行相加)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值