在介绍MySQL二段锁
以前,我须要理清一下概念,即MySQL
二阶段加锁与二阶段提交的区别:数据库
二阶段加锁:用于单机事务中的一致性和隔离性 二阶段提交:用于分布式事务
在一个事务操做中,分为加锁阶段
和解锁阶段
,且全部的加锁操做在解锁操做以前,具体以下图所示:segmentfault
当对记录进行更新操做或者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
协议相似,每发送完一个分组就中止发送,等待对方的确认,在收到确认后再发送下一个分组),既然经过网络进行通讯,就必然会有延迟,两种方案的网络通讯时序图以下:分布式
因为商品库存每每是最致命的热点,是整个服务的热点。若是采用第一种方案的话,TPS
理论上能够提高3rt/rt=3
倍。而这是在一个事务中只有3条SQL的状况,理论上多一条SQL就多一个rt时间。性能
另外,当更新操做到达数据库的那个点,才算加锁成功。commit
到达数据库的时候才算解锁成功。因此,更新操做的前半个rt
和commit
操做的后半个rt
都不计算在整个锁库存的时间内。优化
从上面的例子能够看出,在一个事务操做中,将对最热点记录的操做放到事务的最后面,这样能够显著地提升服务的吞吐量
。spa
咱们能够将一些简单的判断逻辑写到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
延时图以下:
从上图能够看出,加了update谓词之后,一个事务少了1rt的锁记录时间(update谓词和select for update对记录加的都是X锁,因此效果是同样的)。
加锁SQL都或多或少会遇到这个问题。上面的最佳实践中,笔者建议在一个事务中,对记录的加锁按照记录的热点程度升序排列,对与任何会并发的SQL都必须按照相同的顺序来处理,不然会致使死锁,以下图:
合理地写好SQL,对于咱们提升系统的吞吐量相当重要。