美文网首页
MySql-两阶段加锁协议

MySql-两阶段加锁协议

作者: 香港记者mo | 来源:发表于2020-12-17 15:56 被阅读0次

顾名思义,行锁就是针对数据表中行记录的锁。这很好理解,比如事务 A 更新了一行,而这时候事务 B 也要更新同一行,则必须等事务 A 的操作完成后才能进行更新。

在实际情况下,SQL是千变万化、条数不定的,数据库很难在事务中判定什么是加锁阶段,什么是解锁阶段。于是引入了S2PL(Strict-2PL),即: 在事务中只有提交(commit)或者回滚(rollback)时才是解锁阶段,其余时间为加锁阶段。

举个例子。在下面的操作序列中,事务 B 的 update 语句执行时会是什么现象呢?假设字段 id 是表 t 的主键。

实际上事务 B 的 update 语句会被阻塞,直到事务 A 执行 commit 之后,事务 B 才能继续执行。事务 A 持有的两个记录的行锁,都是在 commit 的时候才释放的。在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

两阶段加锁对性能的影响,下面两种不同的扣减库存的方案:

方案1:begin;// 扣减库存update t_inventory set count=count-5 where id= ${id} and count>=5;// 锁住用户账户表select * from t_user_account where user_id=123 for update;// 插入订单记录insert into t_trans; commit;

方案2:begin;// 锁住用户账户表select * from t_user_account where user_id=123 for update;// 插入订单记录insert into t_trans;// 扣减库存update t_inventory set count=count-5 where id=${id} and count>=5;commit;

两者方案的时序如下图所示:

由于库存往往是最重要的热点,是整个系统的瓶颈。那么如果采用第二种方案的话,tps应该理论上能够提升3rt/rt=3倍。这还仅仅是业务就只有三条SQL的情况下,多一条sql就多一次rt,就多一倍的时间。

根据两阶段锁协议,不论你怎样安排语句顺序,所有的操作需要的行锁都是在事务提交的时候才释放的。所以,如果你把更新库存 安排在最后,那么库存这一行的锁时间就最少。这就最大程度地减少了事务之间的锁等待,提升了并发度。

值得注意的是:

在更新到数据库的那个时间点才算锁成功,提交到数据库的时候才算解锁成功,这两个round_trip的前半段是不会计算在内的:

从上面的例子中,可以看出,需要把最热点的记录,放到事务最后,这样可以显著的提高吞吐量。更进一步:越热点记录离事务的终点越近(无论是commit还是rollback)

避免死锁

这也是任何SQL加锁不可避免的。上文提到了按照记录Key的热度在事务中倒序排列。 那么写代码的时候任何可能并发的SQL都必须按照这种顺序来处理,不然会造成死锁。如下图所示: 

但是当业务场景复杂,依然会有死锁的可能,当出现死锁以后,有两种策略:

一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。

另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。

在 InnoDB 中,innodb_lock_wait_timeout 的默认值是 50s,意味着如果采用第一个策略,当出现死锁以后,第一个被锁住的线程要过 50s 才会超时退出,然后其他线程才有可能继续执行。对于在线服务来说,这个等待时间往往是无法接受的。但是,我们又不可能直接把这个时间设置成一个很小的值,比如 1s。这样当出现死锁的时候,确实很快就可以解开,但如果不是死锁,而是简单的锁等待呢?所以,超时时间设置太短的话,会出现很多误伤。

所以,正常情况下我们还是要采用第二种策略,即:主动死锁检测,而且 innodb_deadlock_detect 的默认值本身就是 on。主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。

你可以想象一下这个过程:每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。

当有热点数据被并发更新的话,每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。

热点行更新的解决策略: 降低并发度 :

1. 拆行,一行拆多行,减少锁的冲突,以账户为例,可以考虑放在多条记录上,比如 10 个记录,账户总额等于这 10 个记录的值的总和。这样每次要给账户加金额的时候,随机选其中一条记录来加。这样每次冲突概率变成原来的 1/10,可以减少锁等待个数,也就减少了死锁检测的 CPU 消耗。但其实这类方案需要根据业务逻辑做详细设计。如果账户余额可能会减少,比如退款逻辑,那么这时候就需要考虑当一部分行记录变成 0 的时候,代码要有特殊处理。

 2. Server 层限流,限制同一时间进入更新的线程数 ,比如在应用网关层限流,或是使用sentinel之类的组件

3. 关闭死锁监测(关闭的弊端是可能超时较多)

相关文章

  • MySql-两阶段加锁协议

    MySql-两阶段加锁协议 前言 此篇博客主要是讲述MySql(仅限innodb)的两阶段加锁(2PL)协议,而非...

  • MySql-两阶段加锁协议

    顾名思义,行锁就是针对数据表中行记录的锁。这很好理解,比如事务 A 更新了一行,而这时候事务 B 也要更新同一行,...

  • AQS --- 融会贯通

    一、ReentrantLock 加锁过程简介 加锁可以分为三个阶段: 尝试加锁; 加锁失败,线程入AQS队列; 线...

  • 两阶段提交协议

    https://www.ibm.com/support/knowledgecenter/en/SSGU8G_12....

  • 两阶段提交协议(2PC)、三阶提交协议(3PC)

    两阶段提交协议(2PC)、三阶提交协议(3PC) 2PC 二阶段提交协议是将事务的提交过程拆分为两个阶段来执行,分...

  • 分布式事务

    对分布式事务及两阶段提交、三阶段提交的理解 关于分布式事务、两阶段提交协议、三阶提交协议

  • 分布式事务

    基于XA协议的两阶段提交方案(2pc) XA 规范的基础是两阶段提交协议:第一阶段是表决阶段,所有参与者都将本事务...

  • 二阶段提交、三阶段提交、paxos协议

    1、二阶段提交协议: 二阶段协议分为两步,分别是投票阶段、提交阶段。 投票阶段:事务提议的协调者向所有参与者发出事...

  • 分布式事务

    XA分布式事务协议 - 两阶段提交 & 三阶段提交 两阶段提交 两阶段提交存在的缺点 三阶段提交

  • 分布式事务一致性

    两阶段提交协议 两阶段提交协议(Two-phase Commit,2PC)经常被用来实现分布式事务。一般分为协调器...

网友评论

      本文标题:MySql-两阶段加锁协议

      本文链接:https://www.haomeiwen.com/subject/yqyzgktx.html