SELECT… FOR UPDATE 排他鎖的實現(xiàn)
1. SELECT…FOR UPDATE 是什么?作用是什么?
select for update 即排他鎖,排他鎖又稱為寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其他鎖并存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務是可以對數據就行讀取和修改。
作用:保證數據的一致性,為了在查詢時,避免其他用戶對該表進行插入,修改或刪除等操作,造成表數據的不一致性。
2. MYSQL中如何查詢是否存在鎖信息?相關SQL
這里只講述和排他鎖有關內容。
2.1MYSQL INFORMATION_SCHEMA 數據庫
INFORMATION_SCHEMA 是mysql自帶的元數據數據庫INNODB_TRX是MYSQL中事務和鎖相關的表INNODB_TRX表提供了當前INNODB引擎內每個事務的信息(除只讀事務外),包括當一個事務啟動,事務是否在等待一個鎖,以及正在執(zhí)行的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 創(chuàng)建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層接口及其實現(xiàn)類
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 接口類及其實現(xiàn)類
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 該類實現(xiàn)了ApplicationRunner接口,其含義是在SpringBoot工程啟動之后,執(zhí)行的操作。該類必須注入到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 調用事務方法繼續(xù)調試,
整個調用流程如下:創(chuàng)建事務——> 調用run方法 -> commit提交事務。

進入run方法:根據檢索條件查詢內容,并對其內容設置類排他鎖。

LOG信息如下:

查詢mysql中是否存在對應的排他鎖信息
-- 執(zhí)行如下信息,查看是否存在Lock信息 SELECT trx_id, trx_state, trx_started, trx_rows_locked FROM INFORMATION_SCHEMA.INNODB_TRX;
結果如下:

證明:數據庫中已經存在排他鎖信息,證明該加鎖方式是OK的,并在大概在第2行。
在數據庫中,再次執(zhí)行以下SQL,進行查詢,得到結果為null。證書排他鎖未釋放之前,再次枷鎖時,返回內容為null,因此select…for update 加鎖之后,未釋放之前,再次加鎖時,返回的lockedConfig內容時null。

到此這篇關于SELECT… FOR UPDATE 排他鎖的實現(xiàn)的文章就介紹到這了,更多相關SELECT… FOR UPDATE 排他鎖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MYSQL 的10大經典優(yōu)化案例場景實戰(zhàn)
在應用開發(fā)的早期,數據量少,開發(fā)人員開發(fā)功能時更重視功能上的實現(xiàn),隨著生產數據的增長,很多SQL語句開始暴露出性能問題,對生產的影響也越來越大,有時可能這些有問題的SQL就是整個系統(tǒng)性能的瓶頸。本文我們就聊聊MYSQL的優(yōu)化2021-09-09

