Mysql 锁机制


一、关于锁

不少人在开发的时候,应该很少会注意到这些锁的问题,也很少会给程序加锁(除了库存这些对数量准确性要求极高的情况下),即使我们不会这些锁知识,我们的程序在一般情况下还是可以跑得好好的。因为这些锁数据库隐式帮我们加了,只会在某些特定的场景下才需要手动加锁。

锁的分类

  • 按照锁的粒度划分:行锁、表锁、页锁
  • 按照锁的使用方式划分:共享锁、排它锁(悲观锁的一种实现)
  • 还有两种思想上的锁:悲观锁、乐观锁。
  • InnoDB中有几种行级锁类型:Record Lock、Gap Lock、Next-key Lock

MySQL的锁机制最显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM 和 MEMORY 存储引擎采用的是表级锁(table-level locking);BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。

对于UPDATE、DELETE、INSERT语句,InnoDB会自动给涉及数据集加排他锁 (X) MyISAM 在执行查询语句 SELECT 前,会自动给涉及的所有表加读锁,在执行增、删、改操作前,会自动给涉及的表加写锁,这个过程并不需要用户干预。

二、行锁

行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。有可能会出现死锁的情况。 行级锁按照使用方式分为共享锁和排他锁。

A、共享锁 (读锁)

将数据变为只读形式,不能进行更新,即读取锁定。

假如事务T对数据对象A加上读锁,则事务T可以读A 也能修改A;其他事务只能再对A加读锁,而不能加写锁,直到事务T释放A上的读锁。这样保证了其他事务可以读取A,但在T释放A上的读锁之前,不能对A做任何修改。

语法:

select ... lock in share mode;

B、排他锁 (写锁)

允许获取到排他锁的事务更新数据,阻止其他事务获取相同数据集的共享读锁和排他写锁,即写入锁定。

假如事务T对数据对象 A 加上写锁,事务 T 可以读 A 也可以修改 A,其他事务不能再对 A 加任何锁,直到T释放 A 上的锁。

语法:

select ... for update

C、加锁方式

MYSQL Innodb引擎默认的修改数据语句:update、delete、insert都会自动给涉及到的数据加上排他锁。

select 操作默认不会加任何锁类型,可以使用 select … for update 加排他锁,使用 select … lock in share mode 加共享锁。

所以加过排他锁的数据行,其他事务中是不能修改其数据的,也不能通过for update和lock in share mode锁的方式查询数据;但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。

D、Innodb行锁实现方式

Innodb行锁是通过给索引上的索引项加锁来实现的,而Oracle是通过给数据块中对相应数据行加锁来实现的。

这也就意味着,只要有通过索引条件来检索数据,InnoDB才使用行级锁,否者InnoDB将使用表锁!

MySQL的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如 MyISAM引擎就不支持行锁。不支持行锁意味着并发控制只能使用表锁,对于这种引擎的表,同 一张表上任何时刻只能有一个更新在执行,这就会影响到业务并发度。InnoDB是支持行锁的, 这也是MyISAM被InnoDB替代的重要原因之一。

三、表锁

表级锁是 mysql 锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大,因为同 一张表上任何时刻只能有一个更新在执行。被大部分的 mysql 引擎支持,MyISAM 和 InnoDB 都支持表级锁,但是 InnoDB 默认的是行级锁。

注意:

  • InnoDB的行锁是基于索引实现的,如果不通过索引访问数据,InnoDB会使用表锁。
  • 在不同的隔离级别下,InnoDB的锁机制和一致性读策略不同。

四、意向共享锁和意向排他锁

意向锁是 InnoDB 数据操作之前自动加的,不需要用户干预。

A、意向共享锁

表示事务准备给数据行加入共享锁,即一个数据行在加共享锁之前必须要先取得该表的IS锁。

B、意向排他锁

表示事务准备给数据行加入排他锁,即一个数据行在加排他锁之前必须要先取得该表的IX锁。

五、乐观锁与悲观锁

1. 悲观锁

在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作对某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度 (悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

mysql 中实现悲观锁的具体流程:

在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。
如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

总而言之就是一句话:mysql中悲观锁的实现是通过排他锁来实现的。

悲观锁的优点和不足:

悲观锁实际上是采取了“先取锁在访问”的策略,为数据的处理安全提供了保证,但是在效率方面,由于额外的加锁机制产生了额外的开销,并且增加了死锁的机会。并且降低了并发性;当一个事务加锁一行数据的时候,其他事务必须等待该事务提交之后,才能操作这行数据。

2. 乐观锁

关系数据库管理系统里,乐观并发控制 (又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。

乐观锁 (Optimistic Locking) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。

mysql实现乐观锁一般来说有2种方式:

A、使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式

一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。

当提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,就进行更新操作,否则认为是过期数据,正在提交的事务会进行回滚。

B、第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段

名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致就更新,否则就是版本冲突。

乐观锁的优点和不足

​ 乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。

乐观锁和MVCC的区别?

在数据库中,并发控制是指在多个用户/进程/线程同时对数据库进行操作时,如何保证事务的一致性和隔离性的,同时最大程度地并发。

当多个用户/进程/线程同时对数据库进行操作时,会出现3种冲突情形:

  • 读-读,不存在任何问题
  • 读-写,有隔离性问题,可能遇到脏读(会读到未提交的数据) ,幻影读等。
  • 写-写,可能丢失更新

要解决冲突,一种办法是是锁,即基于锁的并发控制,比如 2PL 两阶段锁协议,这种方式开销比较高,而且无法避免死锁。而基于无锁的并发控制有两种方式:就是 MVCC 多版本并发控制和 OCC 乐观并发控制,这两种方式分别解决上面的第2,3种情况。

多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读

乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制,认为事务间争用没有那么多,所以先进行修改,在提交事务前,检查一下事务开始后,有没有新提交改变,如果没有就提交,如果有就放弃并重试。乐观并发控制类似自旋锁。乐观并发控制适用于低数据争用,写冲突比较少的环境。

多版本并发控制可以结合基于锁的并发控制来解决写-写冲突,即MVCC+2PL,也可以结合乐观并发控制来解决写-写冲突。

六、减少锁冲突和死锁的措施

  • 尽量使用索引访问数据,使加锁更精确,避免表锁出现,从而减少锁冲突的概率;
  • 给记录集显式加锁时,一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁;
  • 不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少发生死锁的概率;
  • 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响;
  • 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;
  • 对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在森林中麋了鹿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值