Backend Development 20 min read

Solving Product Overselling in High‑Concurrency Scenarios: Seven Implementation Methods

This article analyzes the overselling problem that occurs during high‑concurrency flash‑sale (seckill) operations and presents seven concrete solutions—including improved lock placement, AOP‑based locking, three types of database locks, optimistic locking, a blocking‑queue approach, and a Disruptor queue—complete with SpringBoot 2.5.7 code samples, performance test results, and practical recommendations.

Top Architect
Top Architect
Top Architect
Solving Product Overselling in High‑Concurrency Scenarios: Seven Implementation Methods

1. Introduction

High‑concurrency situations are common in internet companies. This article uses a product‑flash‑sale (seckill) scenario to simulate such conditions, providing all source code, scripts, and test cases at the end.

Environment: SpringBoot 2.5.7, MySQL 8.0, MybatisPlus, Swagger 2.9.2

Simulation tool: JMeter

Simulation flow: Reduce inventory → Create order → Simulate payment

2. Product Flash‑Sale – Overselling

Typical code adds @Transactional and a lock inside the service layer, but testing with 1,000 concurrent requests for 100 items still produces overselling because the lock is released before the transaction commits.

@ApiOperation(value="秒杀实现方式——Lock加锁")
@PostMapping("/start/lock")
public Result startLock(long skgId){
    // ... lock logic ...
    return Result.ok();
}

The premature lock release leads to inventory being deducted multiple times.

3. Solving the Overselling Problem

The core issue is that the lock is released before the transaction finishes. The following seven methods move the lock acquisition to an earlier stage.

3.1 Method 1 – Improved Lock (Lock in Controller)

@PostMapping("/start/lock")
public Result startLock(long skgId){
    lock.lock(); // lock before business logic
    try{
        // business logic
    }finally{
        lock.unlock(); // release after transaction
    }
    return Result.ok();
}

Testing with various concurrency‑product ratios shows that when the request count exceeds the product count, no under‑selling occurs, but when they are comparable, occasional under‑selling may appear.

3.2 Method 2 – AOP‑Based Lock

Define a custom annotation @ServiceLock and an aspect that locks before the annotated method executes.

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceLock {}

@Aspect
public class LockAspect {
    private static final Lock lock = new ReentrantLock(true);
    @Pointcut("@annotation(com.example.ServiceLock)")
    public void lockAspect(){}
    @Around("lockAspect()")
    public Object around(ProceedingJoinPoint p) throws Throwable {
        lock.lock();
        try { return p.proceed(); }
        finally { lock.unlock(); }
    }
}

This keeps the locking logic separate from business code, making it more elegant.

3.3 Method 3 – Pessimistic Lock (FOR UPDATE)

@Select("SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

Row‑level lock is held until the transaction commits.

3.4 Method 4 – Pessimistic Lock (UPDATE Statement)

@Update("UPDATE seckill SET number=number-1 WHERE seckill_id=#{skgId} AND number>0")
int updateSecondKillById(@Param("skgId") long skgId);

Directly updates the row, acquiring a table lock for the duration of the statement.

3.5 Method 5 – Optimistic Lock

Uses a version field to detect concurrent updates.

@Update("UPDATE seckill SET number=number-#{number}, version=version+1 WHERE seckill_id=#{skgId} AND version=#{version}")
int updateSecondKillByVersion(@Param("number") int number,
                              @Param("skgId") long skgId,
                              @Param("version") int version);

High contention leads to many update failures; not recommended for large‑scale seckill.

3.6 Method 6 – Blocking Queue

Requests are placed into a fixed‑size LinkedBlockingQueue ; a consumer thread processes them sequentially.

public class SecondKillQueue {
    static final int QUEUE_MAX_SIZE = 100;
    static BlockingQueue
blockingQueue = new LinkedBlockingQueue<>(QUEUE_MAX_SIZE);
    // singleton getter, produce/consume methods omitted for brevity
}

Queue length must be larger than product count to avoid under‑selling.

3.7 Method 7 – Disruptor Queue

Uses LMAX Disruptor for ultra‑low‑latency event processing.

public class SecondKillEventFactory implements EventFactory
{
    public SecondKillEvent newInstance() { return new SecondKillEvent(); }
}

public class SecondKillEventProducer {
    private static final EventTranslatorVararg
translator =
        (event, seq, args) -> { event.setSeckillId((Long)args[0]); event.setUserId((Long)args[1]); };
    // publish logic omitted
}

Performance improves, but overselling can still occur due to the gap between enqueue and dequeue.

4. Summary

Methods 1 and 2 solve the lock‑timing issue by acquiring the lock before the transaction starts.

Methods 3‑5 rely on database‑level locks; method 5 (optimistic) performs the worst under high contention.

Methods 6‑7 use queue‑based processing; they require careful exception handling (no uncaught RuntimeException) and a queue size larger than the product count to avoid under‑selling.

All seven approaches have been verified with three test scenarios: (1) 1,000 concurrent requests for 100 items, (2) 1,000 concurrent requests for 1,000 items, and (3) 2,000 concurrent requests for 1,000 items. Future work includes exploring distributed solutions.

Source code repository: https://github.com/Hofanking/springboot-second-skill-example

concurrencyDistributedSystemslockingSpringBootseckill
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.