SELECT... FOR UPDATE 排他锁
SELECT… FOR UPDATE 排他锁
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。
更多推荐
所有评论(0)