悲觀鎖的應用場景
悲觀鎖的基本思想是假設并發沖突會發生,因此在操作數據時會先鎖定數據,直到完成操作并提交事務后才釋放鎖。這種方式適用于寫操作較多、并發沖突可能性較高的場景。
高寫入比例的數據庫操作:如果系統中有很多寫操作,并且這些寫操作可能會頻繁地相互干擾,那么使用悲觀鎖可以有效避免數據不一致的問題。
對數據一致性要求高的場景:比如金融交易系統,銀行轉賬,高并發點贊等,需要確保在任何時刻的數據都是一致的,不允許出現臟讀、不可重復讀等問題。
樂觀鎖的應用場景
樂觀鎖則假定并發沖突不會經常發生,因此它不會在開始操作時就鎖定資源,而是在提交更新時檢查是否有其他事務已經修改了該數據。如果檢測到沖突,則拒絕此次操作。樂觀鎖更適用于讀多寫少的環境。
讀操作遠多于寫操作的場景:例如在線閱讀平臺、新聞網站等,這類應用主要以讀取信息為主,很少會有數據修改的需求,采用樂觀鎖可以減少鎖帶來的性能損耗。
低沖突概率的環境:當系統預期不同事務之間很少會對同一數據進行修改時,使用樂觀鎖可以獲得更好的性能表現。比如庫存管理系統中,對于非熱銷商品的庫存調整。
選擇哪種鎖機制應基于具體的應用場景以及系統對性能和一致性的需求來決定。在實際開發過程中,還需要考慮死鎖預防、鎖的粒度等因素。
點贊悲觀鎖實現
以下是基于 Java 和 MyBatis-Plus 實現悲觀鎖的點贊功能的完整代碼示例。
1. 數據庫表設計
假設我們有一個 articles 表,包含以下字段:
id: 文章唯一標識。
title: 文章標題。
content: 文章內容。
likes: 點贊數。
sql
深色版本
CREATE TABLE articles (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
likes INT DEFAULT 0
);
2. 配置 MyBatis-Plus 和數據庫連接
2.1 添加依賴
在 pom.xml 文件中添加 MyBatis-Plus 和 MySQL 驅動依賴(與之前一致)。
2.2 配置數據庫連接
在 application.yml 中配置數據庫連接信息(與之前一致)。
3. 實體類和 Mapper 接口
3.1 創建實體類
java
深色版本
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;@Data
@TableName("articles")
public class Article {@TableIdprivate Long id;private String title;private String content;private Integer likes;
}
3.2 創建 Mapper 接口
java
深色版本
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;@Mapper
public interface ArticleMapper extends BaseMapper<Article> {/*** 悲觀鎖查詢文章*/@Select("SELECT * FROM articles WHERE id = #{id} FOR UPDATE")Article selectForUpdate(Long id);/*** 更新點贊數*/@Update("UPDATE articles SET likes = likes + 1 WHERE id = #{id}")int incrementLikes(Long id);
}
4. 實現點贊功能
4.1 Service 層實現
悲觀鎖的核心在于使用 FOR UPDATE 鎖定數據行,確保在事務提交前其他事務無法修改該行數據。
java
深色版本
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class ArticleService {@Autowiredprivate ArticleMapper articleMapper;/*** 使用悲觀鎖實現點贊*/@Transactional // 開啟事務public String likeArticle(Long articleId) {// 使用悲觀鎖查詢文章(鎖定該行)Article article = articleMapper.selectForUpdate(articleId);if (article == null) {return "Article not found";}// 更新點贊數int rows = articleMapper.incrementLikes(articleId);if (rows > 0) {return "Like successful, new likes: " + (article.getLikes() + 1);} else {return "Failed to update likes";}}
}
4.2 Controller 層實現
java
深色版本
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/articles")
public class ArticleController {@Autowiredprivate ArticleService articleService;/*** 點贊接口*/@PostMapping("/{id}/like")public String likeArticle(@PathVariable Long id) {return articleService.likeArticle(id);}
}
5. 測試接口
啟動 Spring Boot 應用后,可以通過以下方式測試點贊功能:
請求示例
bash
深色版本
POST http://localhost:8080/articles/1/like
響應結果
成功時返回:“Like successful, new likes: X”
如果文章不存在,返回:“Article not found”
6. 核心原理
悲觀鎖的工作流程:
在查詢文章時,使用 FOR UPDATE 鎖定目標數據行。
在事務提交之前,其他事務無法對該行數據進行修改。
更新點贊數后提交事務,釋放鎖。
SQL 示例:
sql
深色版本
– 查詢并鎖定數據行
SELECT * FROM articles WHERE id = 1 FOR UPDATE;
– 更新點贊數
UPDATE articles SET likes = likes + 1 WHERE id = 1;
適用場景:
適用于寫操作較多且并發沖突概率較高的場景。
例如,在高并發環境下對同一篇文章進行頻繁點贊時,可以避免數據不一致的問題。
7. 注意事項
性能開銷:
悲觀鎖會鎖定數據行,可能導致其他事務阻塞,影響系統性能。
因此,只有在確實需要嚴格保證數據一致性時才使用悲觀鎖。
死鎖風險:
如果多個事務同時嘗試鎖定不同的資源,可能會導致死鎖。
需要合理設計事務邏輯,盡量減少死鎖的可能性。
事務管理:
必須確保在事務中執行鎖定和更新操作,否則鎖定不起作用。
通過以上代碼,我們可以利用悲觀鎖實現點贊功能,確保在高并發場景下的數據一致性。
點贊樂觀鎖實現
以下是使用 Java 和 MyBatis-Plus 實現樂觀鎖的代碼示例。我們將基于 Spring Boot 和 MyBatis-Plus 框架完成點贊功能。
1. 數據庫表設計
假設我們有一個 articles 表,包含以下字段:
id: 文章唯一標識。
title: 文章標題。
content: 文章內容。
likes: 點贊數。
version: 版本號(用于樂觀鎖)。
sql
深色版本
CREATE TABLE articles (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
likes INT DEFAULT 0,
version INT DEFAULT 0
);
2. 配置 MyBatis-Plus 和數據庫連接
2.1 添加依賴
在 pom.xml 文件中添加 MyBatis-Plus 和 MySQL 驅動依賴:
xml
深色版本
org.springframework.boot
spring-boot-starter-web
<!-- MyBatis-Plus -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version>
</dependency><!-- MySQL Driver -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency><!-- Lombok (Optional for code simplification) -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
2.2 配置數據庫連接 在 application.yml 中配置數據庫連接信息:
yaml
深色版本
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
3. 實體類和 Mapper 接口
3.1 創建實體類
使用 @Version 注解來標識版本號字段。
java
深色版本
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;@Data
@TableName("articles")
public class Article {@TableIdprivate Long id;private String title;private String content;private Integer likes;@Versionprivate Integer version; // 版本號字段,用于樂觀鎖
}
3.2 創建 Mapper 接口
繼承 MyBatis-Plus 提供的 BaseMapper 接口。
java
深色版本
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface ArticleMapper extends BaseMapper<Article> {
}
4. 配置樂觀鎖插件
在 Spring Boot 啟動類中配置 MyBatis-Plus 的樂觀鎖插件。
java
深色版本
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyBatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加樂觀鎖插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}
}
5. 實現點贊功能
5.1 Service 層實現
編寫點贊邏輯,使用樂觀鎖更新點贊數。
java
深色版本
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class ArticleService {@Autowiredprivate ArticleMapper articleMapper;public String likeArticle(Long articleId) {// 查詢文章信息Article article = articleMapper.selectById(articleId);if (article == null) {return "Article not found";}// 更新點贊數和版本號article.setLikes(article.getLikes() + 1);// 使用 MyBatis-Plus 自動處理樂觀鎖int rows = articleMapper.updateById(article);if (rows > 0) {return "Like successful, new likes: " + article.getLikes();} else {return "Optimistic lock conflict, please try again";}}
}
5.2 Controller 層實現
提供 RESTful API 接口。
java
深色版本
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/articles")
public class ArticleController {@Autowiredprivate ArticleService articleService;@PostMapping("/{id}/like")public String likeArticle(@PathVariable Long id) {return articleService.likeArticle(id);}
}
6. 測試接口
啟動 Spring Boot 應用后,可以通過以下方式測試點贊功能:
請求示例
bash
深色版本
POST http://localhost:8080/articles/1/like
響應結果
成功時返回:“Like successful, new likes: X”
如果發生樂觀鎖沖突,返回:“Optimistic lock conflict, please try again”
7. 核心原理
樂觀鎖的核心思想:
在更新數據時,MyBatis-Plus 會自動檢查 version 字段是否匹配。
如果匹配,則更新數據并遞增 version。
如果不匹配,則更新失敗,返回受影響行數為 0。
SQL 示例:
sql
深色版本
UPDATE articles
SET likes = likes + 1, version = version + 1
WHERE id = 1 AND version = current_version;
適用場景:
適用于讀多寫少且沖突概率較低的場景,如點贊、評論、瀏覽量統計等。
通過以上代碼,我們可以輕松實現基于 MyBatis-Plus 的樂觀鎖功能,確保在高并發場景下的數據一致性。
銀行轉賬悲觀鎖實現
在銀行轉賬的場景中,悲觀鎖是一種非常合適的并發控制機制。由于銀行轉賬涉及資金的流動,數據一致性至關重要,因此使用悲觀鎖可以確保在事務完成之前,相關賬戶不會被其他事務修改。
以下是基于 Java 和 MyBatis-Plus 實現銀行轉賬功能的完整代碼示例,其中使用了悲觀鎖來保證事務的安全性。
1. 數據庫表設計
假設我們有一個 accounts 表,用于存儲用戶賬戶信息:
id: 賬戶唯一標識。
user_name: 用戶名。
balance: 賬戶余額。
sql
深色版本
CREATE TABLE accounts (
id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(255) NOT NULL,
balance DECIMAL(10, 2) NOT NULL DEFAULT 0.00
);
2. 配置 MyBatis-Plus 和數據庫連接
2.1 添加依賴
在 pom.xml 文件中添加 MyBatis-Plus 和 MySQL 驅動依賴(與之前一致)。
2.2 配置數據庫連接
在 application.yml 中配置數據庫連接信息:
yaml
深色版本
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
3. 實體類和 Mapper 接口
3.1 創建實體類
java
深色版本
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.math.BigDecimal;@Data
@TableName("accounts")
public class Account {@TableIdprivate Long id;private String userName;private BigDecimal balance;
}
3.2 創建 Mapper 接口
java
深色版本
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;@Mapper
public interface AccountMapper extends BaseMapper<Account> {/*** 悲觀鎖查詢賬戶*/@Select("SELECT * FROM accounts WHERE id = #{id} FOR UPDATE")Account selectForUpdate(Long id);/*** 更新賬戶余額*/@Update("UPDATE accounts SET balance = balance + #{amount} WHERE id = #{id}")int updateBalance(Long id, BigDecimal amount);
}
4. 實現轉賬功能
4.1 Service 層實現
悲觀鎖的核心在于使用 FOR UPDATE 鎖定賬戶行,確保在事務提交前其他事務無法修改賬戶余額。
java
深色版本
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.math.BigDecimal;@Service
public class AccountService {@Autowiredprivate AccountMapper accountMapper;/*** 使用悲觀鎖實現轉賬** @param fromAccountId 轉出賬戶 ID* @param toAccountId 轉入賬戶 ID* @param amount 轉賬金額*/@Transactional // 開啟事務public String transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {if (amount.compareTo(BigDecimal.ZERO) <= 0) {return "Invalid transfer amount";}// 查詢轉出賬戶并加鎖Account fromAccount = accountMapper.selectForUpdate(fromAccountId);if (fromAccount == null || fromAccount.getBalance().compareTo(amount) < 0) {return "Insufficient balance or invalid account";}// 查詢轉入賬戶并加鎖Account toAccount = accountMapper.selectForUpdate(toAccountId);if (toAccount == null) {return "Invalid target account";}// 執行轉賬操作accountMapper.updateBalance(fromAccountId, amount.negate()); // 減少轉出賬戶余額accountMapper.updateBalance(toAccountId, amount); // 增加轉入賬戶余額return "Transfer successful";}
}
4.2 Controller 層實現
java
深色版本
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.math.BigDecimal;@RestController
@RequestMapping("/accounts")
public class AccountController {@Autowiredprivate AccountService accountService;/*** 轉賬接口*/@PostMapping("/transfer")public String transfer(@RequestParam Long fromAccountId,@RequestParam Long toAccountId,@RequestParam BigDecimal amount) {return accountService.transfer(fromAccountId, toAccountId, amount);}
}
5. 測試接口
啟動 Spring Boot 應用后,可以通過以下方式測試轉賬功能:
請求示例
bash
深色版本
POST http://localhost:8080/accounts/transfer?fromAccountId=1&toAccountId=2&amount=100.00
響應結果
成功時返回:“Transfer successful”
如果余額不足或賬戶無效,返回相應的錯誤信息。
6. 核心原理
悲觀鎖的工作流程:
在查詢賬戶時,使用 FOR UPDATE 鎖定目標賬戶的數據行。
在事務提交之前,其他事務無法對該賬戶進行修改。
完成轉賬操作后提交事務,釋放鎖。
SQL 示例:
sql
深色版本
– 查詢并鎖定賬戶
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
– 更新賬戶余額
UPDATE accounts SET balance = balance - 100.00 WHERE id = 1;
UPDATE accounts SET balance = balance + 100.00 WHERE id = 2;
適用場景:
適用于寫操作較多且并發沖突概率較高的場景。
例如,在高并發環境下進行銀行轉賬時,可以避免重復扣款或余額不一致的問題。
7. 注意事項
性能開銷:
悲觀鎖會鎖定數據行,可能導致其他事務阻塞,影響系統性能。
因此,只有在確實需要嚴格保證數據一致性時才使用悲觀鎖。
死鎖風險:
如果多個事務同時嘗試鎖定不同的資源,可能會導致死鎖。
需要合理設計事務邏輯,盡量減少死鎖的可能性(例如,按照固定的順序鎖定賬戶)。
事務管理:
必須確保在事務中執行鎖定和更新操作,否則鎖定不起作用。
通過以上代碼,我們可以利用悲觀鎖實現銀行轉賬功能,確保在高并發場景下的數據一致性。