How to Ensure Order Service Idempotency and Prevent Duplicate Orders
This article explains how to achieve idempotent order creation and updates in backend services by using unique request identifiers, database unique constraints, Redis flags, and optimistic locking with version columns, while also addressing the ABA problem with practical code examples and diagrams.
Problem Background
The simplest solution is a DB transaction: when creating an order, inserts into the order table and order‑item table must run in the same transaction.
If 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. Therefore, a distributed interface must guarantee idempotency.
How to Avoid Duplicate Orders
Front‑end can try to prevent duplicate submissions, but network errors and automatic retries in RPC frameworks or gateways mean duplicate requests can still reach the back‑end, so the service itself must ensure idempotency.
2.1 How to Determine a Duplicate Request
Before inserting an order, check the order table for an existing order – but defining “duplicate” in SQL is hard.
Is an order with the same user, product, and price a duplicate? What if the user legitimately places two identical orders?
2.1.1 Each Request Must Have a Unique Identifier
For example, a payment request should include an order ID that can be used only once for successful payment.
2.1.2 Record That the Request Has Been Processed
In MySQL, add a status field or insert a payment record before the actual payment; the presence of this record indicates the request was handled.
2.1.3 Check Before Processing If It Was Already Handled
If an order is already paid, a payment record exists. A duplicate request will attempt to insert the same primary key, triggering a unique‑key violation and preventing double charging.
Use a pre‑generated global unique order ID as the primary key. The front‑end obtains this ID before the user submits the order, and the same ID is sent with the creation request. The DB’s unique constraint guarantees only one successful INSERT.
In practice, combine this with Redis: store the order ID as a unique key, set it to “paid” after a successful payment record, and check Redis on subsequent requests to reject duplicates.
If a duplicate order causes an INSERT failure, 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.
Correct behavior: the service returns a success response regardless of the duplicate INSERT error.
3 Solving the ABA Problem
3.1 What Is ABA
After an order is paid, the seller fills in a tracking number. If the seller first enters 666, then corrects it to 888, two update requests are sent. If the 666 request succeeds but its response is lost, a retry may resend 666, overwriting the correct 888 value.
3.2 Solution
Add a version column to the order table. Each time the order is read, the version is returned to the client, which must send it back with the update request.
The service compares the provided version with the current one:
If they differ, reject the update.
If they match, update the data and increment the version in the same transaction.
UPDATE orders set tracking_number = 666, version = version + 1 WHERE version = 8;The version check guarantees that no other modification occurred between reading and updating the record; otherwise the update fails and the client must retry with the latest version.
Using this versioning, the ABA scenario is handled:
The 666 update succeeds; the subsequent 888 request carries a stale version and is rejected.
If 888 carries the new version, it succeeds, and any later retry of 666 fails because the version has changed.
This ensures the database state matches the user’s feedback, achieving idempotent updates and eliminating ABA.
Summary
Create orders idempotently by pre‑generating a global unique order ID and relying on the DB’s primary‑key uniqueness.
Update orders idempotently by using an optimistic‑locking version column to detect and prevent ABA scenarios.
These two idempotency techniques can be applied to any service that writes to a database with a primary key.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
