Ensuring Idempotency in Order Services: Preventing Duplicate Orders and Solving the ABA Problem
This article explains how to prevent duplicate order submissions and address the ABA concurrency issue by using database transactions, unique constraints, request identifiers, and version‑based optimistic locking to achieve reliable idempotent behavior in backend order services.
Problem Background
The simplest solution is to use a DB transaction: when creating an order, insert rows into the order table and order‑item table within the same transaction.
When the Order service calls the Pay service and a network timeout occurs, the Order service may retry, causing the Pay service to receive the same payment request twice on different nodes; thus a distributed interface must guarantee idempotency.
How to Avoid Duplicate Orders
Front‑end pages can prevent duplicate form submissions, but network errors and automatic retries in RPC frameworks or gateways can still cause repeated requests, so the core issue remains ensuring service‑side idempotency.
2.1 How to Determine a Request Is Duplicate
Before inserting an order, check the order table for duplicates, but defining “duplicate order” in SQL is difficult.
Is an order with the same user, product, and price a duplicate? What if the user intentionally places two identical orders?
To achieve idempotency, the following practices are required:
2.1.1 Each request must have a unique identifier
For example, a payment request should include an order ID, and an order ID can only be paid successfully once.
2.1.2 After processing a request, record that it has been handled
In MySQL, add a status field or insert a payment record before processing the payment.
2.1.3 When receiving a request, check whether it has been processed before
If an order has already been paid, a payment record exists; a duplicate request will attempt to insert a payment record with the same unique key, causing a duplicate‑key error and preventing double charging.
Using MySQL’s primary‑key uniqueness, an INSERT with an existing primary key will fail, so by providing the primary key on insert you can enforce idempotent order creation.
Generate a globally unique order number via an “orderId generation” API; the front‑end obtains this orderId before submitting the order, and the same orderId is used for all subsequent insert attempts, relying on the DB’s unique constraint.
In practice, combine this with Redis: store the orderId as a unique key, mark it as paid after a successful payment record insertion, and reject further payments for the same orderId.
When a duplicate payment request arrives, the Redis key order_id will have the value payed , indicating the order has already been processed.
If a duplicate order causes an INSERT into t_order to fail, the Order service should not return the error to the front‑end; otherwise the user may see a failure message even though the order was created.
Solution to the ABA Problem
ABA occurs when an order’s tracking number is updated twice (e.g., 666 then 888) and a retry of the earlier request reverts the data incorrectly.
3.2 Solution
Add a version column to the order table. Each query returns the current version, and the client includes this version in the update request.
When updating, compare the provided version with the stored version; if they match, perform the update and increment the version within the same transaction.
UPDATE orders set tracking_number = 666, version = version + 1 WHERE version = 8;If the version has changed because another update occurred, the update fails, prompting the client to re‑fetch the latest version and retry.
This version‑based optimistic locking prevents both duplicate updates and ABA scenarios, ensuring the database state matches the user’s view.
Summary
Pre‑generate a unique order number and rely on the DB’s unique constraint to achieve idempotent order creation.
Use a version‑column mechanism for updates, checking the version and incrementing it atomically to solve the ABA problem and guarantee idempotent updates.
These two idempotency techniques can be applied to any service that persists data in a database with a primary‑key table.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.