SELECT… FOR UPDATE 排他鎖的實現(xiàn)
1. SELECT…FOR UPDATE 是什么?作用是什么?
select for update 即排他鎖,排他鎖又稱為寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其他鎖并存,如一個事務獲取了一個數(shù)據(jù)行的排他鎖,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務是可以對數(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中事務和鎖相關(guān)的表INNODB_TRX表提供了當前INNODB引擎內(nèi)每個事務的信息(除只讀事務外),包括當一個事務啟動,事務是否在等待一個鎖,以及正在執(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掃描和開啟事務
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)容是什么?
斷點位置:
- 事務攔截器(使用AOP的方式對@Transactional注解進行處理)inovke方法處斷點: org.springframework.transaction.interceptor.TransactionInterceptor#invoke
- ExclusiveLocksRunner.java run 方法
Debug如圖所示,證明ExclusiveLocksRunner.java中run 方法上@Transcational 注解是有效的。
進入invokeWithinTransaction 調(diào)用事務方法繼續(xù)調(diào)試,
整個調(diào)用流程如下:創(chuàng)建事務——> 調(diào)用run方法 -> commit提交事務。
進入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-03node 多種方法連接mysql數(shù)據(jù)庫(最新推薦)
mysql是一個流行的第三方模塊,可以通過npm安裝,在Node.js 中,有多種方法可以連接 MySQL 數(shù)據(jù)庫,本文通過實例代碼講解node 多種方法連接mysql數(shù)據(jù)庫的示例代碼,感興趣的朋友跟隨小編一起看看吧2023-07-07mysql中自增auto_increment功能的相關(guān)設(shè)置及問題
mysql中的自增auto_increment功能相信每位phper都用過,本文就為大家分享一下mysql字段自增功能的具體查看及設(shè)置方法2012-12-12MySQL 線上數(shù)據(jù)庫清理數(shù)據(jù)的方法
這篇文章主要介紹了MySQL 線上數(shù)據(jù)庫清理數(shù)據(jù)的方法,幫助大家更好的理解和學習使用MySQL,感興趣的朋友可以了解下2021-03-03MySQL登錄時出現(xiàn) Access denied for user ‘
今天打開mysql的時候突然提示:Access denied for user 'root'@'localhost' (using password: YES) 在網(wǎng)上搜索了很多文章,本文就來做一下總結(jié),介紹了幾種場景的解決方法,感興趣的可以了解一下2024-03-03