MySQL的事务并发控制和锁

一、并发控制介绍

    无论何时,只要有多个查询需要在同一个时间修改数据,都会产生并发控制的问题,上面的脏读不可重复读等均是并发可能产生的问题。此时,我们需要加锁来进行并发控制,保证数据的一致性等。并发控制是一个内容庞大的话题,只是简要讨论mysql如何实现并发读写控制。

二、锁类型

  1. 乐观锁和悲观锁

  (1)悲观锁

    悲观锁,它对于数据被外界修改持保守态度,认为数据随时会修改,所以整个数据处理中需要将数据加锁。悲观锁一般都是依靠关系数据库提供的锁机制,事实上关系数据库中的行锁,表锁不论是读锁或写锁都是悲观锁。悲观锁按使用性质和作用范围有如下锁类型:

  a. 按使用性质分:

  共享锁(Share locks简记为S锁)

  也称读锁,事务A对对象T加S锁,其他事务也只能对T加S,不能加X锁,多个事务可以同时读,但不能有写操作,直到A释放S锁。

  用法:SELECT ... LOCK IN SHARE MODE;

  在查询语句后面增加LOCK IN SHARE MODE,MySQL 就会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。

  排它锁(Exclusivelocks简记为X锁)

  也称写锁,事务A对对象T加X锁以后,其他事务不能对T加任何锁,只有事务A可以读写对象T直到A释放X锁。

  用法:SELECT ... FOR UPDATE;

  在查询语句后面增加FOR UPDATE,MySQL 就会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。

  更新锁(简记为U锁)

  用来预定要对此对象施加X锁,它允许其他事务读,但不允许再施加U锁或X锁;当被读取的对象将要被更新时,则升级为X锁,主要是用来防止死锁的。因为使用共享锁时,修改数据的操作分为两步,首先获得一个共享锁,读取数据,然后将共享锁升级为排它锁,然后再执行修改操作。这样如果同时有两个或多个事务同时对一个对象申请了共享锁,在修改数据的时候,这些事务都要将共享锁升级为排它锁。这些事务都不会释放共享锁而是一直等待对方释放,这样就造成了死锁。如果一个数据在修改前直接申请更新锁,在数据修改的时候再升级为排它锁,就可以避免死锁。

  意向锁(Intention Lock)

  意向锁是表级锁,其设计目的主要是为了在一个事务中揭示下一行将要被请求锁的类型,即当一个事务在需要获取资源的锁定时,如果该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。InnoDB 中的两个表锁:

  • 意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁;
  • 意向排他锁(IX):类似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。

  意向锁是 InnoDB 自动加的,不需要用户干预。

  b. 按作用范围(锁的粒度)分:

  • 行锁:锁的作用范围是行级别。
  • 表锁:锁的作用范围是整张表。

  注:对于INSERTUPDATEDELETE,InnoDB 会自动给涉及的数据加排他锁;对于一般的SELECT语句,InnoDB 不会加任何锁,事务可以通过以下语句显式加共享锁或排他锁。

  (2)乐观锁

    顾名思义,就是很乐观,每次自己操作数据的时候认为没有人回来修改它,所以不去加锁,但是在更新的时候会去判断在此期间数据有没有被修改,需要用户自己去实现。既然都有数据库提供的悲观锁可以方便使用为什么要使用乐观锁呢?对于读操作远多于写操作的时候,大多数都是读取,这时候一个更新操作加锁会阻塞所有读取,降低了吞吐量。最后还要释放锁,锁是需要一些开销的,我们只要想办法解决极少量的更新操作的同步问题。换句话说,如果是读写比例差距不是非常大或者你的系统没有响应不及时,吞吐量瓶颈问题,那就不要去使用乐观锁,它增加了复杂度,也带来了额外的风险。

  通过版本号来实现

  就是给数据增加一个版本标识,在数据库上就是表中增加一个version字段,每次更新把这个字段加1,读取数据的时候把version读出来,更新的时候比较version,如果还是开始读取的version就可以更新了,如果现在的version比老的version大,说明有其他事务更新了该数据,并增加了版本号,这时候得到一个无法更新的通知,用户自行根据这个通知来决定怎么处理,比如重新开始一遍。这里的关键是判断version和更新两个动作需要作为一个原子单元执行,否则在你判断可以更新以后正式更新之前有别的事务修改了version,这个时候你再去更新就可能会覆盖前一个事务做的更新,造成第二类丢失更新,所以你可以使用update … where … and version=”old version”这样的语句,根据返回结果是0还是非0来得到通知,如果是0说明更新没有成功,因为version被改了,如果返回非0说明更新成功。

  通过时间戳来实现

  和版本号基本一样,只是通过时间戳来判断而已,注意时间戳要使用数据库服务器的时间戳不能是业务系统的时间。

  通过待更新字段来实现

  和版本号方式相似,只是不增加额外字段,直接使用有效数据字段做版本控制信息,因为有时候我们可能无法改变旧系统的数据库表结构。假设有个待更新字段叫count,先去读取这个count,更新的时候去比较数据库中count的值是不是我期望的值(即开始读的值),如果是就把我修改的count的值更新到该字段,否则更新失败。java的基本类型的原子类型对象如AtomicInteger就是这种思想。

  通过所有字段来实现

  和待更新字段类似,只是使用所有字段做版本控制信息,只有所有字段都没变化才会执行更新。

  2. 死锁

  死锁,就是产生了循环等待链条,我等待你的资源,你却等待我的资源,我们都相互等待,谁也不释放自己占有的资源,导致无线等待下去。

  解决死锁的办法:

  一是innodb_lock_wait_timeout 等待锁超时回滚事务: 直观方法是在两个事务相互等待时,当一个等待时间超过设置的某一阀值时,对其中一个事务进行回滚,另一个事务就能继续执行。这种方法简单有效,在innodb中,参数innodb_lock_wait_timeout用来设置超时时间。二是wait-for graph算法来主动进行死锁检测: innodb还提供了wait-for graph算法来主动进行死锁检测,每当加锁请求无法立即满足需要并进入等待时,wait-for graph算法都会被触发。

  避免死锁:

  • 以固定的顺序访问表和行。比如两个更新数据的事务,事务A 更新数据的顺序 为1,2;事务B更新数据的顺序为2,1。这样更可能会造成死锁。
  • 大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。
  • 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
  • 降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
  • 为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。

  3. 锁的粒度

    任何时候,在给定的资源上,锁粒度越小,锁定的数据量越大,并发程度越小,但是性能开销大。所谓的锁策略,就是在锁的开销和数据的安全性之间寻求平衡。

  (1)表锁(table lock)

    表锁是mysql最基本的锁策略,也是开销最小的锁,它会锁定整个表;

    具体情况是:若一个用户正在执行写操作,会获取排他的“写锁”,这可能会锁定整个表,阻塞其他用户的读、写操作;若一个用户正在执行读操作,会先获取共享锁“读锁”,这个锁运行其他读锁并发的对这个表进行读取,互不干扰。只要没有写锁的进入,读锁可以是并发读取统一资源的。通常发生在DDL语句\DML不走索引的语句中,比如这个DML update table set columnA=”A” where columnB=“B”,如果columnB字段不存在索引(或者不是组合索引前缀),会锁住所有记录也就是锁表,如果语句的执行能够执行一个columnB字段的索引,那么会锁住满足where的行(行锁如下)。

  (2)行锁(row lock)

    行锁可以最大限度的支持并发处理,当然也带来了最大开销,顾名思义,行锁的粒度实在每一条行数据。

  4. 两段锁协议

    innodb采用的是两阶段锁定协议(tow-phase locking protocal),在事务执行过程中,随时都可以执行锁定,innodb会根据隔离级别在需要的时候自动加锁,锁也只有在执行commit或者rollback时才会被释放,并且所有的锁是在同一时刻被释放。

  5. 多版本并发控制    

    MVCC(multiple-version-concurrency-control)是个行级锁的变种,它在普通读情况下避免了加锁操作,因此开销更低。 虽然实现不同,但通常都是实现非阻塞读,对于写操作只锁定必要的行。MVCC的实现,是通过保存数据在某个时间点的快照来实现的,具体请看:https://www.cnblogs.com/chenloveslife/p/9649457.html

猜你喜欢

转载自www.cnblogs.com/chenloveslife/p/9649434.html