本文以转账操作为例,实现并测试乐观锁和悲观锁。
全部代码:https://github.com/imcloudfloating/Lock_Demo
GitHub Page:https://cloudli.top
死锁问题
当 A, B 两个账户同时向对方转账时,会出现如下情况:
时刻 | 事务 1 (A 向 B 转账) | 事务 2 (B 向 A 转账) |
---|---|---|
T1 | Lock A | Lock B |
T2 | Lock B (由于事务 2 已经 Lock A,等待) | Lock A (由于事务 1 已经 Lock B,等待) |
由于两个事务都在等待对方释放锁,于是死锁产生了,解决方案:按照主键的大小来加锁,总是先锁主键较小或较大的那行数据。
建立数据表并插入数据(MySQL)
create table account ( id int auto_increment primary key, deposit decimal(10, 2) default 0.00 not null, version int default 0 not null ); INSERT INTO vault.account (id, deposit, version) VALUES (1, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (2, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (3, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (4, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (5, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (6, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (7, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (8, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES <p>本文来源gao!daima.com搞$代!码#网#</p>(9, 1000, 0); INSERT INTO vault.account (id, deposit, version) VALUES (10, 1000, 0);
Mapper 文件
悲观锁使用 select … for update,乐观锁使用 version 字段。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.cloud.demo.mapper.AccountMapper"> <select id="selectById" resultType="com.cloud.demo.model.Account"> select * from account where id = #{id} </select> <update id="updateDeposit" keyProperty="id" parameterType="com.cloud.demo.model.Account"> update account set deposit=#{deposit}, version = version + 1 where id = #{id} and version = #{version} </update> <select id="selectByIdForUpdate" resultType="com.cloud.demo.model.Account"> select * from account where id = #{id} for update </select> <update id="updateDepositPessimistic" keyProperty="id" parameterType="com.cloud.demo.model.Account"> update account set deposit=#{deposit} where id = #{id} </update> <select id="getTotalDeposit" resultType="java.math.BigDecimal"> select sum(deposit) from account; </select> </mapper>
Mapper 接口
@Component public interface AccountMapper { Account selectById(int id); Account selectByIdForUpdate(int id); int updateDepositWithVersion(Account account); void updateDeposit(Account account); BigDecimal getTotalDeposit(); }
Account POJO
@Data public class Account { private int id; private BigDecimal deposit; private int version; }