Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL分布式锁

MySQL实现分布式锁

作者:俗世游子​​​​​​​

这篇文章主要介绍了MySQL实现分布式锁,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下

基于MySQL分布式锁实现原理及代码

工欲善其事必先利其器,在基于MySQL实现分布式锁之前,我们要先了解一点MySQL锁自身的相关内容

MySQL锁

我们知道:锁是计算机协调多个进程或者线程并发访问同一资源的机制,而在数据库中,除了传统的机器资源的争用之外,存储下来的数据也属于供用户共享的资源,所以如何保证数据并发的一致性,有效性是每个数据库必须解决的问题。

除此之外,锁冲突也是影响数据库并发性能的主要因素,所以锁对于数据库而言就显得非常重要,也非常复杂。

存储引擎是MySQL中非常重要的底层组件,主要用来处理不同类型的SQL操作,其中包括创建,读取,删除和修改操作。在MySQL中提供了不同类型的存储引擎,根据其不同的特性提供了不同的存储机制,索引和锁功能。

根据show engines;能够列出MySQL下支持的存储引擎

如果没有特殊指定,那么在MySQL8.0中会设置InnoDB为默认的存储引擎

在实际工作中,根据需求选择最多的两种存储引擎分别为:

所以我们主要针对这两种类型来介绍MySQL的锁

InnoDB

InnoDB支持多粒度锁定,可以支持行锁,也可以支持表锁。如果没有升级锁粒度,那么默认情况下是以行锁来设计的。

关于行锁和表锁的介绍:

这里没法说明那种锁最好,只有合适不合适

在行级锁中,可以分为两种类型

共享锁

共享锁又称为读锁,允许其他事务读取被锁定的对象,也可以在其上获取其他共享锁,但不能写入。

举个例子:

下面是关于共享锁的具体实现,关键代码:select .. from table lock in share mode

 -- 创建实例表
 create table tb_lock(
     id bigint primary key auto_increment,
     t_name varchar(20)
 ) engine=InnoDB;

开启两个窗口来测试:

session1session2
set autocommit=0;set autocommit=0;
select * from tb_lock where t_name = ‘zs’ lock in share mode; 
 select * from tb_lock where t_name = ‘zs’ lock in share mode;
 select * from tb_lock where t_name = ‘lsp’ lock in share mode;
update tb_lock set t_name = ‘lzs’ where t_name = ‘zs’; 
update tb_lock set t_name = ‘lsp111’ where t_name = ‘lsp’; 
 select * from tb_lock where t_name = ‘zs’;
commit; 

自动提交全部关闭,可以通过select @@autocommit;来查看

通过以上实验,我们总结:

修改,删除,插入会默认对涉及到的数据加上排他锁

排它锁

又叫写锁。只允许获取锁的事务对数据进行操作【更新,删除】,其他事务对相同数据集只能进行读取,不能有跟新或者删除操作。而且也不能在相同数据集获取到共享锁。

没错,就是这么霸道

在MySQL中,想要基于排它锁实现行级锁,就需要对表中索引列加锁,否则的话,排它锁就属于表级锁

下面一一来展示,关键代码:select .. from XX for update

首先是有索引列状态

session1session2
set autocommit=0;set autocommit=0;
select * from tb_lock;select * from tb_lock;
select * from tb_lock where id = 1 for update; 
 select * from tb_lock where id = 1 for update;
select * from tb_lock where id = 2 for update; 
commit; 

通过以上实验,得到结论:

 Lock wait timeout exceeded; try restarting transaction

下面是无索引列状态

session1session2
set autocommit=0;set autocommit=0;
select * from tb_lock;select * from tb_lock;
select * from tb_lock where t_name = ‘ls’ for update; 
 select * from tb_lock where t_name = ‘ls’ for update;
commit 

通过以上实验,得到结论:

接下来我们来看看MyISAM的方式

MyISAM

MyISAM属于表级锁,被用来防止任何其他事务访问表的锁。

其中表锁又分为两种形式

这里我们要注意:表级锁只能防止其他会话进行不适当的读取或写入。

  • 持有WRITE 锁的会话可以执行表级操作,比如DELETE或者TRUNCATE
  • 持有会话READ锁,不能够执行DELETE或者TRUNCATE操作

表共享读锁

不管是READ还是WRITE,都是通过lock table 来获取表锁的,而READ锁拥有如下特性:

那么,接下来我们来看实际操作,关键代码:lock tables table_name read

 create table tb_lock_isam(
     id bigint primary key auto_increment,
     t_name varchar(20)
 ) engine=MyISAM;

开启两个窗口来进行操作:

session1session2
set autocommit=0;set autocommit=0;
LOCK TABLES tb_lock_isam READ; 
select * from tb_lock_isam; 
select * from tb_lock; 
 select * from tb_lock_isam;
 LOCK TABLES tb_lock_isam READ;
 select * from tb_lock_isam;
 select * from tb_lock;
unlock tables;insert into tb_lock_isam(t_name) values(‘ll’);
  

通过以上实战,验证以下结论:

 Table 'tb_lock' was not locked with LOCK TABLES
 Table 'tb_lock_isam' was locked with a READ lock and can't be updated

表独占写锁

WRITE锁的特性和排它锁的特性非常相似,都特别霸道:

还是通过具体实战来进行演示效果,关键代码:lock tables table_name write

session1session2
select * from tb_lock_isam;select * from tb_lock_isam;
lock table tb_lock_isam write; 
select * from tb_lock_isam; 
insert into tb_lock_isam(t_name) values(‘66’); 
 select * from tb_lock_isam;
unlock tables; 

通过以上实战,验证以下结论:

  Table 'tb_index' was not locked with LOCK TABLES'

注意

MyISAM在执行查询语句之前,会自动给涉及的所有表加读锁,在执行更新操作前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要使用命令来显式加锁

分布式锁实现

既然已经了解到了MySQL锁相关内容,那么我们就来看看如何实现,首先我们需要创建一张数据表

当然,只需要初始化创建一次

 create table if not exists fud_distribute_lock(
     id bigint unsigned primary key auto_increment,
     biz varchar(50) comment '业务Key'
     unique(biz)
 ) engine=innodb;

在其中,biz是为了区分不同的业务,也可以理解为资源隔离,并且对biz设置唯一索引,也能够防止其锁级别变为表级锁

既然for udpate就是加锁成功,事务提交就自动释放锁,那么这个事情就非常好办了:

 // 省略了构造方法,需要传入DataSource和biz
 ​
 private static final String SELECT_SQL = 
     "SELECT * FROM fud_distribute_lock WHERE `biz` = ? for update";
 private static final String INSERT_SQL = 
     "INSERT INTO fud_distribute_lock(`biz`) values(?)";
 ​
 // 从构造方法中传入
 private final DataSource source;
 private Connection connection;
 ​
 public void lock() {
     PreparedStatement psmt = null;
     ResultSet rs = null;
 ​
     try {
         // while(true); 
         for (; ; ) {
             connection = this.source.getConnection();
             // 关闭自动提交事务
             connection.setAutoCommit(false);
             
             psmt = connection.prepareStatement(SELECT_SQL);
             psmt.setString(1, biz);
             rs = psmt.executeQuery();
             if (rs.next()) {
                 return;
             }
             connection.commit();
             close(connection, psmt, rs);
             // 如果没有相关查询,需要插入
             Connection updConnection = this.source.getConnection();
             PreparedStatement insertStatement = null;
             try {
                 insertStatement = updConnection.prepareStatement(INSERT_SQL);
                 insertStatement.setString(1, biz);
                 if (insertStatement.executeUpdate() == 1) {
                     LOGGER.info("创建锁记录成功");
                 }
             } catch (Exception e) {
                 LOGGER.error("创建锁记录异常:{}", e.getMessage());
             } finally {
                 close(insertStatement, updConnection);
             }
         }
     } catch (Exception e) {
         LOGGER.error("lock异常信息:{}", e.getMessage());
         throw new BusException(e);
     } finally {
         close(psmt, rs);
     }
 }
 ​
 public void unlock() {
     try {
         // 事务提交之后自动解锁
         connection.commit();
         close(connection);
     } catch (Exception e) {
         LOGGER.error("unlock异常信息:{}", e.getMessage());
         throw new BusException(e);
     }
 }
 ​
 public void close(AutoCloseable... closeables) {
     Arrays.stream(closeables).forEach(closeable -> {
         if (null != closeable) {
             try {
                 closeable.close();
             } catch (Exception e) {
                 LOGGER.error("close关闭异常:{}", e.getMessage());
             }
         }
     });
 }

难点:为什么需要for(;

如果一个请求是第一次进来的,比如biz=order,在这个表中是不会存储order这条记录,那么select ...for update就不会生效,所以就需要先将order插入到表记录中,也就是执行insert操作。

insert执行成功之后,记录select...for update,这样获取锁才能生效

总结

基于MySQL的分布式锁在实际开发过程中很少使用,但是我们还是要有一个思路在。那么本节针对MySQL的分布式锁实现到这里就结束了,掌握了MySQL的基础锁,那么就会非常简单了。

到此这篇关于MySQL实现分布式锁的文章就介绍到这了,更多相关MySQL分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文