SELECT… FOR UPDATE 排他鎖的實現(xiàn)
1. SELECT…FOR UPDATE 是什么?作用是什么?
select for update 即排他鎖,排他鎖又稱為寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其他鎖并存,如一個事務(wù)獲取了一個數(shù)據(jù)行的排他鎖,其他事務(wù)就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務(wù)是可以對數(shù)據(jù)就行讀取和修改。
作用:保證數(shù)據(jù)的一致性,為了在查詢時,避免其他用戶對該表進行插入,修改或刪除等操作,造成表數(shù)據(jù)的不一致性。
2. MYSQL中如何查詢是否存在鎖信息?相關(guān)SQL
這里只講述和排他鎖有關(guān)內(nèi)容。
2.1MYSQL INFORMATION_SCHEMA 數(shù)據(jù)庫
INFORMATION_SCHEMA 是mysql自帶的元數(shù)據(jù)數(shù)據(jù)庫INNODB_TRX是MYSQL中事務(wù)和鎖相關(guān)的表INNODB_TRX表提供了當前INNODB引擎內(nèi)每個事務(wù)的信息(除只讀事務(wù)外),包括當一個事務(wù)啟動,事務(wù)是否在等待一個鎖,以及正在執(zhí)行的SQL語句。
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
3. SELECT…FOR UPDATE 怎么使用?如何驗證?
這里使用mysql數(shù)據(jù)庫為例。
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工程,結(jié)構(gòu)目錄如下
├─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文件內(nèi)容
引入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 連接驅(qū)動 -->
<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文件內(nèi)容如下: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掃描和開啟事務(wù)
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內(nèi)容是什么?
斷點位置:
- 事務(wù)攔截器(使用AOP的方式對@Transactional注解進行處理)inovke方法處斷點: org.springframework.transaction.interceptor.TransactionInterceptor#invoke
- ExclusiveLocksRunner.java run 方法
Debug如圖所示,證明ExclusiveLocksRunner.java中run 方法上@Transcational 注解是有效的。

進入invokeWithinTransaction 調(diào)用事務(wù)方法繼續(xù)調(diào)試,
整個調(diào)用流程如下:創(chuàng)建事務(wù)——> 調(diào)用run方法 -> commit提交事務(wù)。

進入run方法:根據(jù)檢索條件查詢內(nèi)容,并對其內(nèi)容設(shè)置類排他鎖。

LOG信息如下:

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

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

到此這篇關(guān)于SELECT… FOR UPDATE 排他鎖的實現(xiàn)的文章就介紹到這了,更多相關(guān)SELECT… FOR UPDATE 排他鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mysql數(shù)據(jù)庫sql優(yōu)化原則(經(jīng)驗總結(jié))
這里的原則 只是針對mysql數(shù)據(jù)庫,其他的數(shù)據(jù)庫 某些是殊途同歸,某些還是存在差異。我總結(jié)的也是mysql普遍的規(guī)則,對于某些特殊情況得特殊對待。在構(gòu)造sql語句的時候養(yǎng)成良好的習慣2014-03-03
node 多種方法連接mysql數(shù)據(jù)庫(最新推薦)
mysql是一個流行的第三方模塊,可以通過npm安裝,在Node.js 中,有多種方法可以連接 MySQL 數(shù)據(jù)庫,本文通過實例代碼講解node 多種方法連接mysql數(shù)據(jù)庫的示例代碼,感興趣的朋友跟隨小編一起看看吧2023-07-07
mysql中自增auto_increment功能的相關(guān)設(shè)置及問題
mysql中的自增auto_increment功能相信每位phper都用過,本文就為大家分享一下mysql字段自增功能的具體查看及設(shè)置方法2012-12-12
MySQL 線上數(shù)據(jù)庫清理數(shù)據(jù)的方法
這篇文章主要介紹了MySQL 線上數(shù)據(jù)庫清理數(shù)據(jù)的方法,幫助大家更好的理解和學習使用MySQL,感興趣的朋友可以了解下2021-03-03
MySQL登錄時出現(xiàn) Access denied for user ‘
今天打開mysql的時候突然提示:Access denied for user 'root'@'localhost' (using password: YES) 在網(wǎng)上搜索了很多文章,本文就來做一下總結(jié),介紹了幾種場景的解決方法,感興趣的可以了解一下2024-03-03

