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&hellip;for update 是否有效?

  • select&hellip;for update 加锁之后,未释放之前,再次加锁时,返回的lockedConfig内容是什么?

断点位置:

  • 事务 * (使用AOP的方式对@Transactional注解进行处理)inovke方法处断点: org.springframework.transaction.interceptor.TransactionInterceptor#invoke

  • ExclusiveLocksRunner.java run 方法

Debug如图所示,证明ExclusiveLocksRunner.java中run 方法上@Transcational 注解是有效的。

SELECT… FOR UPDATE 排他锁的实现

进入invokeWithinTransaction 调用事务方法继续调试,

整个调用流程如下:创建事务&mdash;&mdash;> 调用run方法 -> commit提交事务。

SELECT… FOR UPDATE 排他锁的实现

进入run方法:根据检索条件查询内容,并对其内容设置类排他锁。

SELECT… FOR UPDATE 排他锁的实现

LOG信息如下:

SELECT… FOR UPDATE 排他锁的实现

查询mysql中是否存在对应的排他锁信息

-- 执行如下信息,查看是否存在Lock信息
SELECT trx_id, trx_state, trx_started, trx_rows_locked  FROM INFORMATION_SCHEMA.INNODB_TRX;

结果如下:

SELECT… FOR UPDATE 排他锁的实现

证明:数据库中已经存在排他锁信息,证明该加锁方式是OK的,并在大概在第2行。

在数据库中,再次执行以下SQL,进行查询,得到结果为null。证书排他锁未释放之前,再次枷锁时,返回内容为null,因此select&hellip;for update 加锁之后,未释放之前,再次加锁时,返回的lockedConfig内容时null。

SELECT… FOR UPDATE 排他锁的实现

来源:https://blog.csdn.net/qq_23312117/article/details/123810339

标签:SELECT,FOR,UPDATE,排他锁
0
投稿

猜你喜欢

  • YOLOv5车牌识别实战教程(八)Web应用与API开发

    2022-12-29 23:44:22
  • asp中文件与文件夹常用处理函数(文件后缀、创建文件等)

    2011-02-20 11:00:00
  • Python脚本实现虾米网签到功能

    2021-11-23 14:37:53
  • 如何利用pandas工具输出每行的索引值、及其对应的行数据

    2022-02-03 04:11:23
  • ASP面向对象编程探讨及比较

    2008-04-12 07:16:00
  • asp如何读取注册表的信息?

    2009-11-19 21:18:00
  • 使用Python文件读写,自定义分隔符(custom delimiter)

    2021-12-06 08:17:51
  • javaScript 删除字符串空格多种方法小结

    2024-05-02 16:10:33
  • FCKeditor新版本发布,并更名为CKeditor

    2009-09-08 13:09:00
  • JQuery的Ajax请求实现局部刷新的简单实例

    2024-05-02 17:05:17
  • 如何用Pytorch搭建一个房价预测模型

    2022-12-16 17:09:21
  • PHP在线打包下载功能示例

    2024-06-05 09:40:17
  • vue使用nprogress加载路由进度条的方法

    2024-05-02 17:02:22
  • python openvc 裁剪、剪切图片 提取图片的行和列

    2022-07-03 15:29:40
  • Navicat连接MySQL时出现的连接失败问题及解决

    2024-01-16 00:22:13
  • 10个简化PHP开发的工具

    2023-07-14 18:02:05
  • 一文带你解密Python可迭代对象的排序问题

    2023-11-26 00:53:13
  • Python动态配置管理Dynaconf的实现示例详解

    2021-01-24 19:35:06
  • 适合各种浏览器的js拖动层

    2007-10-22 22:43:00
  • MySQL通过自定义函数实现递归查询父级ID或者子级ID

    2024-01-19 03:30:07
  • asp之家 网络编程 m.aspxhome.com