浅析MySQL二段锁

背景

在介绍MySQL二段锁以前,我须要理清一下概念,即MySQL二阶段加锁与二阶段提交的区别:数据库

二阶段加锁:用于单机事务中的一致性和隔离性
二阶段提交:用于分布式事务

何为二段锁

在一个事务操做中,分为加锁阶段解锁阶段,且全部的加锁操做在解锁操做以前,具体以下图所示:segmentfault

alt text

加锁时机

当对记录进行更新操做或者select for update(X锁)、lock in share mode(S锁)时,会对记录进行加锁,锁的种类不少,不在此赘述。性能优化

什么时候解锁

在一个事务中,只有在commit或者rollback时,才是解锁阶段。网络

二阶段加锁最佳实践

下面举个具体的例子,来说述二段锁对应用性能的影响,咱们举个库存扣减的例子:并发

方案一:
start transaction;
// 锁定用户帐户表
select * from t_accout where acount_id=234 for update
//生成订单
insert into t_trans;
// 减库存
update t_inventory set num=num-3 where id=${id} and num>=3;
commit;
方案二:
start transaction;
// 减库存
update t_inventory set num=num-3 where id=${id} and num>=3;
// 锁定用户帐户表
select * from t_accout where acount_id=234 for update
//生成订单
insert into t_trans;
commit;

咱们的应用经过JDBC操做数据库时,底层本质上仍是走TCP进行通讯,MySQL协议是一种停-等式协议(和http协议相似,每发送完一个分组就中止发送,等待对方的确认,在收到确认后再发送下一个分组),既然经过网络进行通讯,就必然会有延迟,两种方案的网络通讯时序图以下:分布式

alt text

因为商品库存每每是最致命的热点,是整个服务的热点。若是采用第一种方案的话,TPS理论上能够提高3rt/rt=3倍。而这是在一个事务中只有3条SQL的状况,理论上多一条SQL就多一个rt时间。性能

另外,当更新操做到达数据库的那个点,才算加锁成功。commit到达数据库的时候才算解锁成功。因此,更新操做的前半个rtcommit操做的后半个rt都不计算在整个锁库存的时间内。优化

性能优化

从上面的例子能够看出,在一个事务操做中,将对最热点记录的操做放到事务的最后面,这样能够显著地提升服务的吞吐量spa

select for update 和 update where的最优选择

咱们能够将一些简单的判断逻辑写到update操做的谓词里面,这样能够减小加锁的时间,以下:code

方案一:
start transaction
num = select count from t_inventory where id=234 for update
if count >= 3:
    update t_inventory set num=num-3 where id=234
    commit 
else:
    rollback

方案二:

start transaction:
    int affectedRows = update t_inventory set num=num-3 where id=234 and num>=3
    if affectedRows > 0:
        commit
    else:
        rollback

延时图以下:
alt text

从上图能够看出,加了update谓词之后,一个事务少了1rt的锁记录时间(update谓词和select for update对记录加的都是X锁,因此效果是同样的)。

死锁

加锁SQL都或多或少会遇到这个问题。上面的最佳实践中,笔者建议在一个事务中,对记录的加锁按照记录的热点程度升序排列,对与任何会并发的SQL都必须按照相同的顺序来处理,不然会致使死锁,以下图:

alt text

总结

合理地写好SQL,对于咱们提升系统的吞吐量相当重要。

原文连接

https://segmentfault.com/a/11...

相关文章
相关标签/搜索