【MySql专栏】—— 锁的概念

一、锁的作用

数据库加锁,是为了处理并发问题,在并发下保证数据一致性。

虽然我们在发开的过程中,没有使用锁的情况下,我们的程序也可以正常的执行,那是因为数据库帮我们隐式的添加了锁,这篇博客主要从两个大方面来讲,按照使用方式可以讲锁分为:悲观锁和乐观锁;按照粒度可分为:全局锁,表锁和行锁,并在出现死锁后如何解决。

二、乐观锁和悲观锁

2.1乐观锁和悲观锁的概念

乐观锁是一种思想,实现方法为,表中会有一个版本字段,第一次读取数据的时候会获得这个字段;当处理完我们的逻辑操作后,需要更新数据的时候,会比较这个版本字段是否一致,一致则更新成功,反之则更新失败,每次更新成功后,版本地段默认加1;之所以叫乐观锁,因为每次拿数据的时候,默认数据不会改变,之后在最后进行更新的时候才去判断数据是否更改。

悲观锁顾名思义,和乐观锁比较记忆就是,我的思想和悲观,我认为我拿到的数据一定会被别人修改,为了防止被人修改,当我一拿到数据,我就对它进行加锁,这样别人就无法获得我的数据,只有等我操作完成释放锁后,后面的人才能获得我的数据。悲观锁的实现直接在语句后面加for update即可,例如:

select * from T where id =1 for update

2.2实例分析乐观锁悲观锁的作用:

在MySql的默认的隔离级别下(repeatable read),作如下的操作:

时间用户A用户B

t1

从客户端获得一条数据(18,‘hudong’) 
t2 从客户段获得一条数据(18,‘hudong’)
t3 对获得的数据进行修改(22,‘hudong’)

t4

 将数据提交,更新成功
t5对数据进行修改(24,‘hudong’) 
t6将数据提交,更新成功 

这个时候会出现一个什么现象的,就是用户B的更新操作丢失了,也就是一个事务的更新操作覆盖了别的事务的更新操作。

用悲观锁来解决的话就是,在用户A获得数据的语句上直接加上for update,这样一来就是对这个数据加了写锁,当用户B进行更新操作时,就会失败,无法更新,只有等用户A提交后,才能修改。

select * from T username = ‘hudong’ for update

如果用乐观锁来实现,就需要先在表上加一个控制版本的字段,表结构举例如下:

ageusernameversion
18hudong1

每次获得数据的时候,都会带一个版本号,用户A第一次获得数据时,版本号是1,用户B第一次获得数据时,版本号也是1,当用户B进行了更新操作成功后,会将version修改为2;当用户A再次更新数据时,先比较版本号,版本号不一致,更新失败。

三、全局锁、表锁和行锁

3.1全局锁

全剧锁就是将整个数据库进行加锁操作。加锁命令是Flush tables with read lock,释放锁命令为unlock tables。使用这个命令后,整个数据库的更新语句(数据库的增删改)、数据的定义语句(建表,修改表结构)都无法执行。

全剧锁一般会使用在数据库进行备份的时候使用,但是当我们的数据库进行了主从配置,使用全剧锁也会出现问题,当我们对从库进行备份的时候,从库就不能执行从主库同步过来的binlog,会导致主从延迟。

3.2表级锁

表级锁又分为:表锁和元数据锁(meta data lock,MDL),表锁的加锁命令为 lock tables ... read/write,释放锁命令为unlock tables,lock tables的加锁命令不仅是对别的线程进行加锁,也会自当前线程进行加锁。

对于元数据锁(MDL)不需要显示的使用命令来进行加锁,这个锁是数据库默认帮我们添加的,并且MDL锁,在语句执行开始时申请,但不是在语句结束后马上释放,而是会等到整个数据提交后,锁才会被释放。MDL的作用就是保证表的读写一致,即一个表在做增删改的时候,会加上MDL读锁,对表的结构进行修改时,加MDL写锁。

请求锁模式:

  是否兼容

当前锁模式:

读锁写锁
读锁
写锁

从表中可以看出:读锁之间是不互斥的,读写锁之间和写锁之间是互斥。

3.3行锁

行锁是在数据库的引擎层实现的,在MySql中,InnoDB支持行锁,MyISAM不支持行锁。行锁就是针对表的某一行数据进行加锁,乐观锁和悲观锁也就是行锁的操作粒度,和表锁类似,行锁是在语句执行某行操作的时候才加上的,但并不是语句执行完之后立马释放,而是要等事务结束后才会释放。

因此,当你的事务中需要锁多个行的时候,把最可能造成锁冲突,最影响并发度的锁尽量往后放,举例说明一下:

比如对于淘宝的业务来说,当一个顾客A需要在某个店铺买东西的时候,就需要做如下操作:

        1.顾客A从自己的账户扣除钱;

        2.将钱加入店铺账户;

        3.记录一条日志;

为了保持一致行,这个三个操作需要放在一个事务下执行,当有个顾客B也从店铺中买东西,同样的也是执行这三个步骤,这是对于步骤2来说,就会影响事务的冲突了,根据上面所说,只有在事务提交的时候,锁才会被释放,所以安排执行顺序为 1,3,2才能最大程度的减少店铺账户余额这一行被锁的时间最少,这样可以提高我们的并发操作。

四、死锁和解决方法

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

例如下面这个例子:

事务A事务B

begin;

update T set name=‘hudong’ where id = 1;

begin;
 update T set name = ‘hudong’ where id = 2;
update T set name=‘hudong’ where id = 2; 
 update T set name=‘hudong’ id = 1;

这时,事务A在等待事务B释放id=2的行锁,事务B在等待事务A释放id=1的行锁,这时就出现了死锁现象。当出现死锁时,我们应该怎么解决呢?

1.直接进入等待,直到超时。超时时间可以由innodb_lock_wait_timeout来设置,这个默认值为50s,这个值并不能设置的太小,会出现误伤。

2.发起死锁检测,主动回滚死锁链中的某一个事务,让其他事务可以继续执行,将参数innodb_deadlock_detect设置为on,开启检测,MySql是默认开启的,但是开启这个检测需要额外的开销,因为每个新来的线程,都需要判断自己的加入,会不会导致死锁发生,时间复杂度就是O(n2),当有1000个并发的更新统一行的时候,这个检测需要100万的操作,所以这个时候就需要控制我们的并发量了,我们可以使用中间件,来进行限流处理。

这篇文章就到这里,感兴趣的小伙伴,可关注本专栏,你的关注就是我更新的最大动力MySql专栏

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值