前话
同时使用@Transactional注解和 synchronized或者同时使用@Transactional注解和和分布式锁会造成线程安全问题,因为@Transactional是用AOP实现的,当synchronized里面的方法运行完后,AOP的代码里面的事务提交可能还没运行,此时其他请求可以进去synchronized运行,结果就读到了还未提交的事务的数据,去掉@Transactional注解,使用手动开启事务,并提交,将所有代码都放在 synchronized里面,但是synchronized只能再单击起作用,多机只能使用分布式锁
方案一
使用mysql自带的for update
这样在第一次修改完成前 第二次无法查询 自然就可以保证库存减少和订单数相同
方案二
抢单前将库存查询到redis中 使用redis的decrement来保证库存减少的原子性
然后用定时任务每隔一段时间将redis库存同步到mysql中
方案三
使用synchronized,只能在单机起作用
方案四
使用分布式锁
方案五
使用一条update代替select加update,依靠返回的影响行数,判断有无减库存成功,先查询再根据查询的值来update,由于查询是并发的,查询到快照或者说不是最新版本的值,再更新值就会出问题,但是并发update的话,数据库可以保证加上互斥锁,来保证操作原子,就算不是互斥锁,也会在快照并发修改后进行有效性检验
项目代码 https://gitee.com/chen_yan_ting/springboot_grabbing_orders
数据表结构
本次简单测试只用到两张表 六个字段
项目结构
引入依赖
pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>springboot_grabbing_orders</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!-- mybatis plus 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>
配置文件
application.yml
server:
port: 9000
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/grabbing_orders?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
write-dates-as-timestamps: false
redis:
port: 6379
mybatis-plus:
mapper-locations: classpath*:mapper/**/*Mapper.xml
logging.level.com.itheima.mapper: debug #配置显示执行的sql语句
mybatis-plus构造器代码
运行main 输入表名 自动连接数据库 构造代码
GeneratorCodeConfig.java
package com.itheima.config;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.