Mysql系列的目标是:通过这个系列从入门到全面掌握一个高级开发所需要的全部技能。
欢迎大家加我微信itsoku一起交流java、算法、数据库相关技术。
这是Mysql系列第26篇。
本篇我们使用mysql实现一个分布式锁。
分布式锁的功能
分布式锁使用者位于不同的机器中,锁获取成功之后,才可以对共享资源进行操作
锁具有重入的功能:即一个使用者可以多次获取某个锁
获取锁有超时的功能:即在指定的时间内去尝试获取锁,超过了超时时间,如果还未获取成功,则返回获取失败
能够自动容错,比如:A机器获取锁lock1之后,在释放锁lock1之前,A机器挂了,导致锁lock1未释放,结果会lock1一直被A机器占有着,遇到这种情况时,分布式锁要能够自动解决,可以这么做:持有锁的时候可以加个持有超时时间,超过了这个时间还未释放的,其他机器将有机会获取锁
预备技能:乐观锁
通常我们修改表中一条数据过程如下:
-
t1:select获取记录
R1
-
t2:对
R1进行编辑
-
t3:update
R1
我们来看一下上面的过程存在的问题:
如果A、B两个线程同时执行到t1,他们俩看到的R1的数据一样,然后都对R1进行编辑,然后去执行t3,最终2个线程都会更新成功,后面一个线程会把前面一个线程update的结果给覆盖掉,这就是并发修改数据存在的问题。
我们可以在表中新增一个版本号,每次更新数据时候将版本号作为条件,并且每次更新时候版本号+1,过程优化一下,如下:
-
t1:打开事务start transaction
-
t2:select获取记录
R1,声明变量v=
R1.
version
-
t3:对
R1进行编辑
-
t4:执行更新操作
-
update
R1 set version = version +
1 where user_id=#user_id# and version = #v#;
-
t5:t4中的update会返回影响的行数,我们将其记录在count中,然后根据count来判断提交还是回滚
-
if(count==
1){
-
//提交事务
-
commit;
-
}
else{
-
//回滚事务
-
rollback;
-
}
上面重点在于步骤t4,当多个线程同时执行到t1,他们看到的R1是一样的,但是当他们执行到t4的时候,数据库会对update的这行记录加锁,确保并发情况下排队执行,所以只有第一个的update会返回1,其他的update结果会返回0,然后后面会判断count是否为1,进而对事务进行提交或者回滚。可以通过count的值知道修改数据是否成功了。
上面这种方式就乐观锁。我们可以通过乐观锁的方式确保数据并发修改过程中的正确性。
使用mysql实现分布式锁
建表
我们创建一个分布式锁表,如下
-
DROP
DATABASE
IF
EXISTS javacode2018;
-
CREATE
DATABASE javacode2018;
-
USE javacode2018;
-
DROP
TABLE
IF
EXISTS t_lock;
-
create table
t_lock(
-
lock_key
varchar(
32)
PRIMARY
KEY
NOT
NULL
COMMENT
'锁唯一标志',
-
request_id
varchar(
64)
NOT
NULL
DEFAULT
''
COMMENT
'用来标识请求对象的',
-
lock_count
INT
NOT
NULL
DEFAULT
0
COMMENT
'当前上锁次数',
-
timeout
BIGINT
NOT
NULL
DEFAULT
0
COMMENT
'锁超时时间',
-
version
INT
NOT
NULL
DEFAULT
0
COMMENT
'版本号,每次更新+1'
-
)
COMMENT
'锁信息表';
分布式锁工具类:
-
package com.
itsoku.
sql;
-
import lombok.
Builder;
-
import lombok.
Getter;
-
import lombok.
Setter;
-
import lombok.
extern.
slf4j.
Slf4j;
-
import org.
junit.
Test;
-
import java.
sql.*;
-
import java.
util.
Objects;
-
import java.
util.
UUID;
-
import java.
util.
concurrent.
TimeUnit;
-
/**
-
* 工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
-
* 喜欢的请关注公众号:路人甲Java
-
*/
-
@
Slf4j
-
public
class
LockUtils {
-
//将requestid保存在该变量中
-
static
ThreadLocal<
String> requestIdTL =
new
ThreadLocal<>();
-
/**
-
* 获取当前线程requestid
-
*
-
* @return
-
*/
-
public
static
String
getRequestId(
) {
-