乐观锁与悲观锁的使用场景

[复制链接]
发表于 2025-9-30 00:13:47 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
悲观锁的应用场景
悲观锁的根本头脑是假设并发辩论会发生,因此在操纵数据时会先锁定命据,直到完成操纵并提交事件后才开释锁。这种方式实用于写操纵较多、并发辩论大概性较高的场景。
高写入比例的数据库操纵:如果体系中有许多写操纵,而且这些写操纵大概会频仍地相互干扰,那么使用悲观锁可以有用制止数据不划一的标题。
对数据划一性要求高的场景:比如金融生意业务体系,银行转账,高并发点赞等,必要确保在任何时间的数据都是划一的,不答应出现脏读、不可重复读等标题。
乐观锁的应用场景
乐观锁则假定并发辩论不会常常发生,因此它不会在开始操纵时就锁定资源,而是在提交更新时查抄是否有其他事件已经修改了该数据。如果检测到辩论,则拒绝此次操纵。乐观锁更实用于读多写少的环境。
读操纵远多于写操纵的场景:比方在线阅读平台、消息网站等,这类应用重要以读取信息为主,很少会有数据修改的需求,采取乐观锁可以镌汰锁带来的性能斲丧。
低辩论概率的环境:当体系预期差别事件之间很少会对同一数据举行修改时,使用乐观锁可以得到更好的性能表现。比如库存管理体系中,对于非热销商品的库存调解。
选择哪种锁机制应基于详细的应用场景以及体系对性能和划一性的需求来决定。在实际开发过程中,还必要思量死锁防备、锁的粒度等因素。
必要留意的是select ** for update 是数据库层面的锁,出现标题不好排查,发起使用分布式锁。
点赞悲观锁实现

以下是基于 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
深色版本
  1. import com.baomidou.mybatisplus.annotation.TableId;
  2. import com.baomidou.mybatisplus.annotation.TableName;
  3. import lombok.Data;
  4. @Data
  5. @TableName("articles")
  6. public class Article {
  7.     @TableId
  8.     private Long id;
  9.     private String title;
  10.     private String content;
  11.     private Integer likes;
  12. }
复制代码
3.2 创建 Mapper 接口
java
深色版本
  1. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  2. import org.apache.ibatis.annotations.Mapper;
  3. import org.apache.ibatis.annotations.Select;
  4. import org.apache.ibatis.annotations.Update;
  5. @Mapper
  6. public interface ArticleMapper extends BaseMapper<Article> {
  7.     /**
  8.      * 悲观锁查询文章
  9.      */
  10.     @Select("SELECT * FROM articles WHERE id = #{id} FOR UPDATE")
  11.     Article selectForUpdate(Long id);
  12.     /**
  13.      * 更新点赞数
  14.      */
  15.     @Update("UPDATE articles SET likes = likes + 1 WHERE id = #{id}")
  16.     int incrementLikes(Long id);
  17. }
复制代码
4. 实现点赞功能

4.1 Service 层实现
悲观锁的核心在于使用 FOR UPDATE 锁定命据行,确保在事件提交前其他事件无法修改该行数据。
java
深色版本
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.stereotype.Service;
  3. import org.springframework.transaction.annotation.Transactional;
  4. @Service
  5. public class ArticleService {
  6.     @Autowired
  7.     private ArticleMapper articleMapper;
  8.     /**
  9.      * 使用悲观锁实现点赞
  10.      */
  11.     @Transactional // 开启事务
  12.     public String likeArticle(Long articleId) {
  13.         // 使用悲观锁查询文章(锁定该行)
  14.         Article article = articleMapper.selectForUpdate(articleId);
  15.         if (article == null) {
  16.             return "Article not found";
  17.         }
  18.         // 更新点赞数
  19.         int rows = articleMapper.incrementLikes(articleId);
  20.         if (rows > 0) {
  21.             return "Like successful, new likes: " + (article.getLikes() + 1);
  22.         } else {
  23.             return "Failed to update likes";
  24.         }
  25.     }
  26. }
复制代码
4.2 Controller 层实现
java
深色版本
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.web.bind.annotation.*;
  3. @RestController
  4. @RequestMapping("/articles")
  5. public class ArticleController {
  6.     @Autowired
  7.     private ArticleService articleService;
  8.     /**
  9.      * 点赞接口
  10.      */
  11.     @PostMapping("/{id}/like")
  12.     public String likeArticle(@PathVariable Long id) {
  13.         return articleService.likeArticle(id);
  14.     }
  15. }
复制代码
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
  1. <!-- MyBatis-Plus -->
  2. <dependency>
  3.     <groupId>com.baomidou</groupId>
  4.     <artifactId>mybatis-plus-boot-starter</artifactId>
  5.     <version>3.5.3.1</version>
  6. </dependency>
  7. <!-- MySQL Driver -->
  8. <dependency>
  9.     <groupId>mysql</groupId>
  10.     <artifactId>mysql-connector-java</artifactId>
  11.     <scope>runtime</scope>
  12. </dependency>
  13. <!-- Lombok (Optional for code simplification) -->
  14. <dependency>
  15.     <groupId>org.projectlombok</groupId>
  16.     <artifactId>lombok</artifactId>
  17.     <optional>true</optional>
  18. </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
深色版本
  1. import com.baomidou.mybatisplus.annotation.TableField;
  2. import com.baomidou.mybatisplus.annotation.TableId;
  3. import com.baomidou.mybatisplus.annotation.TableName;
  4. import com.baomidou.mybatisplus.annotation.Version;
  5. import lombok.Data;
  6. @Data
  7. @TableName("articles")
  8. public class Article {
  9.     @TableId
  10.     private Long id;
  11.     private String title;
  12.     private String content;
  13.     private Integer likes;
  14.     @Version
  15.     private Integer version; // 版本号字段,用于乐观锁
  16. }
复制代码
3.2 创建 Mapper 接口
继续 MyBatis-Plus 提供的 BaseMapper 接口。
java
深色版本
  1. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  2. import org.apache.ibatis.annotations.Mapper;
  3. @Mapper
  4. public interface ArticleMapper extends BaseMapper<Article> {
  5. }
复制代码
4. 设置乐观锁插件

在 Spring Boot 启动类中设置 MyBatis-Plus 的乐观锁插件。
java
深色版本
  1. import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
  2. import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. @Configuration
  6. public class MyBatisPlusConfig {
  7.     @Bean
  8.     public MybatisPlusInterceptor mybatisPlusInterceptor() {
  9.         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
  10.         // 添加乐观锁插件
  11.         interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
  12.         return interceptor;
  13.     }
  14. }
复制代码
5. 实现点赞功能

5.1 Service 层实现
编写点赞逻辑,使用乐观锁更新点赞数。
java
深色版本
  1. import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.stereotype.Service;
  4. @Service
  5. public class ArticleService {
  6.     @Autowired
  7.     private ArticleMapper articleMapper;
  8.     public String likeArticle(Long articleId) {
  9.         // 查询文章信息
  10.         Article article = articleMapper.selectById(articleId);
  11.         if (article == null) {
  12.             return "Article not found";
  13.         }
  14.         // 更新点赞数和版本号
  15.         article.setLikes(article.getLikes() + 1);
  16.         // 使用 MyBatis-Plus 自动处理乐观锁
  17.         int rows = articleMapper.updateById(article);
  18.         if (rows > 0) {
  19.             return "Like successful, new likes: " + article.getLikes();
  20.         } else {
  21.             return "Optimistic lock conflict, please try again";
  22.         }
  23.     }
  24. }
复制代码
5.2 Controller 层实现
提供 RESTful API 接口。
java
深色版本
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.web.bind.annotation.*;
  3. @RestController
  4. @RequestMapping("/articles")
  5. public class ArticleController {
  6.     @Autowired
  7.     private ArticleService articleService;
  8.     @PostMapping("/{id}/like")
  9.     public String likeArticle(@PathVariable Long id) {
  10.         return articleService.likeArticle(id);
  11.     }
  12. }
复制代码
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
深色版本
  1. import com.baomidou.mybatisplus.annotation.TableId;
  2. import com.baomidou.mybatisplus.annotation.TableName;
  3. import lombok.Data;
  4. import java.math.BigDecimal;
  5. @Data
  6. @TableName("accounts")
  7. public class Account {
  8.     @TableId
  9.     private Long id;
  10.     private String userName;
  11.     private BigDecimal balance;
  12. }
复制代码
3.2 创建 Mapper 接口
java
深色版本
  1. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  2. import org.apache.ibatis.annotations.Mapper;
  3. import org.apache.ibatis.annotations.Select;
  4. import org.apache.ibatis.annotations.Update;
  5. @Mapper
  6. public interface AccountMapper extends BaseMapper<Account> {
  7.     /**
  8.      * 悲观锁查询账户
  9.      */
  10.     @Select("SELECT * FROM accounts WHERE id = #{id} FOR UPDATE")
  11.     Account selectForUpdate(Long id);
  12.     /**
  13.      * 更新账户余额
  14.      */
  15.     @Update("UPDATE accounts SET balance = balance + #{amount} WHERE id = #{id}")
  16.     int updateBalance(Long id, BigDecimal amount);
  17. }
复制代码
4. 实现转账功能

4.1 Service 层实现
悲观锁的核心在于使用 FOR UPDATE 锁定账户行,确保在事件提交前其他事件无法修改账户余额。
java
深色版本
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.stereotype.Service;
  3. import org.springframework.transaction.annotation.Transactional;
  4. import java.math.BigDecimal;
  5. @Service
  6. public class AccountService {
  7.     @Autowired
  8.     private AccountMapper accountMapper;
  9.     /**
  10.      * 使用悲观锁实现转账
  11.      *
  12.      * @param fromAccountId 转出账户 ID
  13.      * @param toAccountId   转入账户 ID
  14.      * @param amount        转账金额
  15.      */
  16.     @Transactional // 开启事务
  17.     public String transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
  18.         if (amount.compareTo(BigDecimal.ZERO) <= 0) {
  19.             return "Invalid transfer amount";
  20.         }
  21.         // 查询转出账户并加锁
  22.         Account fromAccount = accountMapper.selectForUpdate(fromAccountId);
  23.         if (fromAccount == null || fromAccount.getBalance().compareTo(amount) < 0) {
  24.             return "Insufficient balance or invalid account";
  25.         }
  26.         // 查询转入账户并加锁
  27.         Account toAccount = accountMapper.selectForUpdate(toAccountId);
  28.         if (toAccount == null) {
  29.             return "Invalid target account";
  30.         }
  31.         // 执行转账操作
  32.         accountMapper.updateBalance(fromAccountId, amount.negate()); // 减少转出账户余额
  33.         accountMapper.updateBalance(toAccountId, amount); // 增加转入账户余额
  34.         return "Transfer successful";
  35.     }
  36. }
复制代码
4.2 Controller 层实现
java
深色版本
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.web.bind.annotation.*;
  3. import java.math.BigDecimal;
  4. @RestController
  5. @RequestMapping("/accounts")
  6. public class AccountController {
  7.     @Autowired
  8.     private AccountService accountService;
  9.     /**
  10.      * 转账接口
  11.      */
  12.     @PostMapping("/transfer")
  13.     public String transfer(
  14.             @RequestParam Long fromAccountId,
  15.             @RequestParam Long toAccountId,
  16.             @RequestParam BigDecimal amount) {
  17.         return accountService.transfer(fromAccountId, toAccountId, amount);
  18.     }
  19. }
复制代码
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. 留意事项

性能开销:
悲观锁会锁定命据行,大概导致其他事件壅闭,影响体系性能。
因此,只有在确实必要严格包管数据划一性时才使用悲观锁。
死锁风险:
如果多个事件同时实行锁定差别的资源,大概会导致死锁。
必要公道计划事件逻辑,只管镌汰死锁的大概性(比方,按照固定的次序锁定账户)。
事件管理:
必须确保在事件中实行锁定和更新操纵,否则锁定不起作用。
通过以上代码,我们可以使用悲观锁实现银行转账功能,确保在高并发场景下的数据划一性。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表