SELECT… FOR UPDATE 排他锁的实现
作者:我是最菜程序员 发布时间:2024-01-19 12:30:16
1. SELECT…FOR UPDATE 是什么?作用是什么?
select for update 即排他锁,排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
作用:保证数据的一致性,为了在查询时,避免其他用户对该表进行插入,修改或删除等操作,造成表数据的不一致性。
2. MYSQL中如何查询是否存在锁信息?相关SQL
这里只讲述和排他锁有关内容。
2.1MYSQL INFORMATION_SCHEMA 数据库
INFORMATION_SCHEMA 是mysql自带的元数据数据库INNODB_TRX是MYSQL中事务和锁相关的表INNODB_TRX表提供了当前INNODB引擎内每个事务的信息(除只读事务外),包括当一个事务启动,事务是否在等待一个锁,以及正在执行的SQL语句。
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
3. SELECT…FOR UPDATE 怎么使用?如何验证?
这里使用mysql数据库为例。
3.1 Mysql Config表SQL
-- ----------------------------
-- Table structure for config
-- ----------------------------
DROP TABLE IF EXISTS `config`;
CREATE TABLE `config` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`value` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `name-index`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of config
-- ----------------------------
INSERT INTO `config` VALUES (1, 'result', 'false');
3.2 创建SpringBoot工程,结构目录如下
├─main
│ ├─java
│ │ └─com
│ │ └─xy
│ │ └─springboot
│ │ │ Application.java
│ │ ├─db
│ │ │ ├─dao
│ │ │ │ │ ConfigDao.java
│ │ │ │ ├─impl
│ │ │ │ │ ConfigDaoImpl.java
│ │ │ │ └─mapper
│ │ │ │ ConfigMapper.java
│ │ │ └─entity
│ │ │ Config.java
│ │ └─runner
│ │ ExclusiveLocksRunner.java
│ └─resources
│ │ application.properties
│ └─Mapper
└─ ConfigMapper.xml
3.2 pom.xml文件内容
引入mysql driver、lombok、mybatis-plus等依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xy</groupId>
<artifactId>spring-boot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- 使用mybatis-plus 持久层框架 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- mysql 连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.4 db层接口及其实现类
Mapper映射xml文件内容如下:ConfigMapper.xml
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xy.springboot.db.dao.mapper.ConfigMapper">
<select id="getLockedConfig" resultType="com.xy.springboot.db.entity.Config">
SELECT
*
FROM CONFIG
WHERE name = #{name} AND value = ${value}
FOR UPDATE SKIP LOCKED
</select>
</mapper>
ConfigMapper.java 接口类
package com.xy.springboot.db.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xy.springboot.db.entity.Config;
import org.apache.ibatis.annotations.Param;
public interface ConfigMapper extends BaseMapper<Config> {
Config getLockedConfig(@Param("name") String name, @Param("value") String value);
}
ConfigDao.java 接口类及其实现类
package com.xy.springboot.db.dao;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xy.springboot.db.entity.Config;
/**
* 配置类接口
*/
public interface ConfigDao extends IService<Config> {
/**
* 通过名称和value值,获取增加排他锁的配置
* @param name
* @param value
*/
Config getLockedConfig(String name, String value);
}
package com.xy.springboot.db.dao.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xy.springboot.db.dao.ConfigDao;
import com.xy.springboot.db.dao.mapper.ConfigMapper;
import com.xy.springboot.db.entity.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ConfigDaoImpl extends ServiceImpl<ConfigMapper, Config> implements ConfigDao {
private ConfigMapper configMapper;
@Autowired
public void setConfigMapper(ConfigMapper configMapper) {
this.configMapper = configMapper;
}
@Override
public Config getLockedConfig(String name, String value) {
return configMapper.getLockedConfig(name, value);
}
}
Config.java 实体类
package com.xy.springboot.db.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
/**
* 配置表实体类
*/
@TableName("config")
@Getter
@Setter
public class Config {
private int id;
private String name;
private String value;
}
3.5 Application.java 开启Mapper扫描和开启事务
package com.xy.springboot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@MapperScan(basePackages = "com.xy.springboot.db.dao.mapper")
@EnableTransactionManagement
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.6 [核心] ExclusiveLocksRunner 排他锁Runner
ExclusiveLocksRunner 该类实现了ApplicationRunner接口,其含义是在SpringBoot工程启动之后,执行的操作。该类必须注入到IOC容器中。
package com.xy.springboot.runner;
import com.xy.springboot.db.dao.ConfigDao;
import com.xy.springboot.db.entity.Config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* 排他锁验证
*/
@Slf4j
@Component
public class ExclusiveLocksRunner implements ApplicationRunner {
private static final String RESULT_KEY = "result";
private static final String RESULT = "false";
private ConfigDao configDao;
@Autowired
public void setConfigDao(ConfigDao configDao) {
this.configDao = configDao;
}
@Transactional(rollbackFor = Exception.class)
@Override
public void run(ApplicationArguments args) throws Exception {
Config lockedConfig = configDao.getLockedConfig(RESULT_KEY, RESULT);
if (lockedConfig == null) {
log.error("config is null。because config is locked.");
return;
}
log.info("start doing");
lockedConfig.setValue("true");
configDao.updateById(lockedConfig);
}
}
3.7 疑问&并验证
疑问:
ExclusiveLocksRunner类中run方法上的@Transactional 注解是否起作用?
select…for update 是否有效?
select…for update 加锁之后,未释放之前,再次加锁时,返回的lockedConfig内容是什么?
断点位置:
事务 * (使用AOP的方式对@Transactional注解进行处理)inovke方法处断点: org.springframework.transaction.interceptor.TransactionInterceptor#invoke
ExclusiveLocksRunner.java run 方法
Debug如图所示,证明ExclusiveLocksRunner.java中run 方法上@Transcational 注解是有效的。
进入invokeWithinTransaction 调用事务方法继续调试,
整个调用流程如下:创建事务——> 调用run方法 -> commit提交事务。
进入run方法:根据检索条件查询内容,并对其内容设置类排他锁。
LOG信息如下:
查询mysql中是否存在对应的排他锁信息
-- 执行如下信息,查看是否存在Lock信息
SELECT trx_id, trx_state, trx_started, trx_rows_locked FROM INFORMATION_SCHEMA.INNODB_TRX;
结果如下:
证明:数据库中已经存在排他锁信息,证明该加锁方式是OK的,并在大概在第2行。
在数据库中,再次执行以下SQL,进行查询,得到结果为null。证书排他锁未释放之前,再次枷锁时,返回内容为null,因此select…for update 加锁之后,未释放之前,再次加锁时,返回的lockedConfig内容时null。
来源:https://blog.csdn.net/qq_23312117/article/details/123810339


猜你喜欢
- 热词图很酷炫,也非常适合热点事件,抓住重点,以图文结合的方式表现出来,很有冲击力。下面这段代码是制作热词图的,用到了以下技术:jieba,把
- PyCharm PyCharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试、语法
- 1、python代码实现图片分割成九宫格需要包含的库,没有下载安装的,需要自己安装哦。实现原理很简单,就是用PIL库不断画小区域,切下来存储
- 依赖模块xlwt下载:pip install xlwt后台模块view.py# 导出Excel文件def export_excel(requ
- 程序的功能有了个大体的框架,其实可以自己添加一些功能,比如开始的数据库连接 ,可以先设置变量然后通过INIT() 来选择
- 写在前面:在做表格的时候,难免会碰到做统计的时候。由于在项目中涉及到做统计的功能比较简单,之后也就没有过多的去研究更复杂的,这里简单记录下。
- 上次谈到客户端和服务端的编码“陷阱”,其中对url编码只是提及带过,并没有做深入讨论,事实上由于浏览器环境的复杂和不一致性,我们也很容易掉进
- 将图片读入到Dom中,并将其存为xml文件1、需要命名空间using System.Text;using System.IO;using S
- 1. 引言如果能够将我们的无序数据快速组织成更易读的格式,对于数据分析非常有帮助。 Python 提供了将某些表格数据类型轻松转换为格式良好
- 所谓网页抓取,就是把URL地址中指定的网络资源从网络流中读取出来,保存到本地。 在Python中有很多库可以用来抓取网页,我们先学习urll
- 1 报错类似如下数据库错误: Error querying database. Cause: java.sql.SQLSynta
- 本文实例讲述了python通过装饰器检查函数参数数据类型的方法。分享给大家供大家参考。具体分析如下:这段代码定义了一个python装饰器,通
- 本文实例讲述了php使用pthreads v3多线程实现抓取新浪新闻信息。分享给大家供大家参考,具体如下:我们使用pthreads,来写一个
- 前言 pycharm默认是没有为我们设置模板信息的,但为了更加方便的实现代码管理,以及能够一目
- 一、图像色彩通道拆分import cv2img1 = cv2.imread(r"D:\OpencvTest\example.jpg
- 理由:jquery简单,兼容性好且容易封装。废话不多说,马上开始我们的Jquery插件编写吧。应该有很多人写过类似的插件,我也是有些模仿fl
- 如何获取相邻数据因为项目,所以找到了一些资料并且总结了下关于获取相邻数据的方式。我只找到了以下的.../*获取id值与5相减绝对值最近的数据
- 近年,不论是正在快速增长的直播,远程教育以及IM聊天场景,还是在常规企业级系统中用到的系统提醒,对websocket的需求越来越大,对web
- 数据库配置1.安装数据库:自行安装 我的SQL Server版本为2019 2.登录数
- 前言在Python中定义函数,可以用必选参数、默认参数、可变参数和关键字参数,这4种参数都可以一起使用,或者只用其中某些,但是请注意,参数定