使用数据库进行分布式加锁实现

1.使用数据库进行分布式加锁

行锁以mysql为例,进行举例 :

1.1 for update

在mysql中使用for update获得行锁。

for update是一种行级锁,又叫排它锁,一旦用户对某个行施加了行级锁,则该用户可以更新也可以查询也可以更新被加锁的数据行,其他用户只能查询,不能更新被加锁的数据行,如果其他用户想更新该表中的数据行,则也必须对表施加行级锁。

释放行级:

1.执行提交commit语句.

2.退出数据库

3.程序停止运行.

通常情况下,select语句是不会对数据加锁的,妨碍影响其他DDL或者DML操作。

select…for update是我们经常使用的手工加锁语句。如果查询条件带有主键,会锁行数据,没有则进行锁表操作。针对innodb引擎。

举个例子:

锁行示例:

drop table if exists data_user;


create table data_user(
id bigint(15) not null primary key,
name varchar(50),
code varchar(50)
)engine=innodb  comment='测试用户表';


insert into data_user (id,name,code) values(1,'name1','code1');
insert into data_user (id,name,code) values(2,'name2','code2');
insert into data_user (id,name,code) values(3,'name3','code3');

在命令行1中输入:

mysql> begin;

select * from data_user where  id = 2 for update;
Query OK, 0 rows affected (0.00 sec)

+----+-------+-------+
| id | name  | code  |
+----+-------+-------+
|  2 | name2 | code2 |
+----+-------+-------+
1 row in set (0.01 sec)

mysql> 

在命令行2中输入

mysql> begin;

select * from data_user where  id = 2 for update;
Query OK, 0 rows affected (0.00 sec)

会发现SQL卡住了,这就是锁行了.

然后再命令行1中操作

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

这时命令行2中就可以看到:

mysql> begin;

select * from data_user where  id = 2 for update;
Query OK, 0 rows affected (0.00 sec)

+----+-------+-------+
| id | name  | code  |
+----+-------+-------+
|  2 | name2 | code2 |
+----+-------+-------+
1 row in set (46.07 sec)

之前卡住的信息又恢复了。

这就是行锁最简单的一个演示,如果在使用for update不加主键那将是锁表。

for update实现分布式锁

使用for update实现一个分布式场景下的锁:

先给出数据库的SQL:

drop table if exists goods;

create table goods(
id bigint(15) not null primary key,
name varchar(50),
dataNum int(5)
)engine=innodb  comment='商品信息';

insert into goods values(1,'mac',800);

先看数据库操作:

public class JdbcOperator {
  private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
  private static final String MYSQL_URL =
      "jdbc:mysql://localhost:3306/zsc?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
  private static final String USER_NAME = "root";
  private static final String PASSWORD = "123456";

  static {
    // 加载驱动
    try {
      Class.forName(DRIVER_CLASS);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }

  /**
   * 批量SQL操作
   *
   * @param sqlList
   * @return
   */
  public static int executeSql(List<String> sqlList) {

    int updateRsp = 0;
    try (Connection connection = DriverManager.getConnection(MYSQL_URL, USER_NAME, PASSWORD); ) {
      // 关闭自动提交
      connection.setAutoCommit(false);

      for (int i = 0; i < sqlList.size(); i++) {
        runStatementSql(sqlList.get(i), connection);
      }

      // 完成后提交事务
      connection.commit();
    } catch (SQLException e) {
      e.printStackTrace();
    }

    return updateRsp;
  }

  private static void runStatementSql(String sql, Connection connection) {
    try (PreparedStatement stat = connection.prepareStatement(sql)) {
      stat.execute();
      System.out.println(Thread.currentThread().getId() + "," + sql);
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }
}

调用进行扣减库存操作:

public class Goods {

  public Goods() {}

  /** 商品的库存扣减操作 */
  public void minusGoods(int num) {

    List<String> sqlList = new ArrayList<>();
    sqlList.add("begin;");
    sqlList.add("select * from goods where  id = 1 for update;");
    sqlList.add("update goods set dataNum = dataNum-" + num + " where id = 1;");

    // 扣减库存操作
    JdbcOperator.executeSql(sqlList);
  }
}

订单信息:

public class Orders {

  /** 商品服务 */
  private Goods goods;

  public Orders(Goods goods) {
    this.goods = goods;
  }

  /**
   * 创建订单
   *
   * @return
   */
  public boolean createOrder(int num) {
    // 执行扣减库存操作
    goods.minusGoods(num);
    return true;
  }
}

测试分布式场景下的锁:

public class TestOrdersLock {

  @Test
  public void useOrder() throws InterruptedException {
    int orderNumSum = 800;
    Goods goods = new Goods();

    // 并发进行下单操作
    int maxOrder = 4;

    int count = 0;
    for (int i = 0; i < orderNumSum / maxOrder; i++) {
      CountDownLatch startLatch = new CountDownLatch(maxOrder);
      for (int j = 0; j < maxOrder; j++) {
        TaskThreadPool.INSTANCE.submit(
            () -> {
              startLatch.countDown();

              Orders instance = new Orders(goods);
              instance.createOrder(1);
            });

        count++;
      }
      // 执行等待结果
      try {
        startLatch.await();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    System.out.println("结束,共运行:" + count + "次");

    TaskThreadPool.INSTANCE.shutdown();

    Thread.sleep(500);

    System.out.println("shutdown status:" + TaskThreadPool.INSTANCE.getPool().isShutdown());
  }
}

单元测试的结果在控制台可以看到:

15,begin;
12,begin;
13,begin;
16,begin;
19,begin;
17,begin;
14,begin;
18,begin;
13,select * from goods where  id = 1 for update;
13,update goods set dataNum = dataNum-1 where id = 1;
12,select * from goods where  id = 1 for update;
12,update goods set dataNum = dataNum-1 where id = 1;
15,select * from goods where  id = 1 for update;
15,update goods set dataNum = dataNum-1 where id = 1;
19,select * from goods where  id = 1 for update;
19,update goods set dataNum = dataNum-1 where id = 1;
16,select * from goods where  id = 1 for update;
16,update goods set dataNum = dataNum-1 where id = 1;
17,select * from goods where  id = 1 for update;
17,update goods set dataNum = dataNum-1 where id = 1;
14,select * from goods where  id = 1 for update;
14,update goods set dataNum = dataNum-1 where id = 1;
18,select * from goods where  id = 1 for update;
18,update goods set dataNum = dataNum-1 where id = 1;
......
13,begin;
13,select * from goods where  id = 1 for update;
13,update goods set dataNum = dataNum-1 where id = 1;
结束,共运行:800次
17,begin;
17,select * from goods where  id = 1 for update;
17,update goods set dataNum = dataNum-1 where id = 1;
shutdown status:true

检查数据库的结果

mysql> select * from goods;
+----+------+---------+
| id | name | dataNum |
+----+------+---------+
|  1 | mac  |       0 |
+----+------+---------+
1 row in set (0.02 sec)
劣势

讲完了实现,再来说说这个实现的问题吧。

  1. 锁操作过程中如果没有正确的提交,那将导致数据库表被锁表,其他进程将不能操作锁定的表,直白点,将直接引发生产事故。
  2. for update将占用数据库连接,如果在操作过程中使用时间过长,就容易造成锁超时。
  3. 如果并发量过大,会造成数据库直接宕机。

所以此分布式锁使用务必小心,不推荐使用。

适用的场景

使用数据库的for update的加锁机制适用于并发不是特别大的场景,这是因为数据库所能承载的并发是较小的,由于每个锁就占用一个数据库连接,如果操作时间过长,将可能导致锁超时,使用过程也务必要小心,如果某个线程使用了for update,那么在事务运行过程宕机了,那么这个锁将得不到释放,需要手动处理。如果要给出一个参考的数据的话,此并发在几十。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值