Stock Decrement Locking – Latency Spikes During Concurrent Checkouts
0 reputation · 19 Feb 2021, 10:04 UTC
0 reputation · 19 Feb 2021, 10:04 UTC
WooCommerce’s core stock management uses a row‑level lock (SELECT … FOR UPDATE) when a product’s quantity is decremented during checkout. The lock is intended to serialize concurrent updates to the same product row.
When hundreds of users attempt to purchase the same item simultaneously, the lock can serialize the requests, causing noticeable latency spikes that are only visible under load. In some cases, the lock is released before all updates are committed, which can lead to overselling.
The current implementation does not queue or batch checkout requests, and the REST API for cart updates lacks optimistic concurrency control. Documentation acknowledges potential race conditions but does not prescribe a definitive solution, leaving developers to implement custom strategies.
Is the SELECT … FOR UPDATE lock sufficient to serialize concurrent stock decrements without causing overselling?
What mechanisms does WooCommerce provide to detect and resolve deadlocks during checkout?
Which configuration changes can reduce lock contention and latency for high‑volume stores?
32275 reputation · 19 Feb 2021, 19:10 UTC
Is the SELECT … FOR UPDATE lock sufficient to serialize concurrent stock decrements without causing overselling? In a typical MySQL/MariaDB setup with WooCommerce’s default stock‑decrement transaction, the lock does serialize updates, but it can still allow overselling if the lock is released before the transaction commits (e.g., due to a deadlock, timeout, or improper transaction handling). The lock is a safeguard, not a guarantee.
What mechanisms does WooCommerce provide to detect and resolve deadlocks during checkout? WooCommerce relies on the underlying database engine. MySQL reports deadlocks via SHOW ENGINE INNODB STATUS and logs them in error.log. WooCommerce itself does not retry the transaction automatically; it simply aborts the checkout if a lock timeout or deadlock occurs.
Which configuration changes can reduce lock contention and latency for high‑volume stores? The most effective changes are:
READ COMMITTED for the stock‑decrement query.stock_version column or a lightweight advisory lock).Likely Explanation: Pessimistic row‑level locking causes contention when many users try to buy the same SKU, leading to lock wait timeouts and latency spikes. The lock can be released before the transaction commits, allowing a second transaction to decrement the same row and causing overselling.
Confirmed Fact: Your server logs show repeated Lock wait timeout exceeded; try restarting transaction events during peak checkout periods, and response times jump from ~200 ms to over 2 s.
SELECT @@transaction_isolation;
If it returns REPEATABLE-READ, consider switching to READ COMMITTED for the stock update. Add this to wp-config.php before the decrement query:
add_action( 'init', function() {
global $wpdb;
$wpdb->query( 'SET SESSION transaction_isolation = "READ COMMITTED";' );
} );
stock_version integer column to wp_wc_product_meta_lookup (or the relevant stock table). Update the decrement query to check and increment this column atomically:
UPDATE wp_wc_product_meta_lookup
SET stock_quantity = stock_quantity - 1,
stock_version = stock_version + 1
WHERE product_id = %d AND stock_quantity >= 1 AND stock_version = %d;
If the affected rows count is 0, retry the operation.
SELECT GET_LOCK( CONCAT('stock_', %d), 5 );
-- perform decrement
SELECT RELEASE_LOCK( CONCAT('stock_', %d) );
This keeps the critical section short and avoids long row locks.
WPDB::get_results() failures that return a falsey value and wait 50–200 ms before retrying, up to 3 attempts.
Could you confirm the transaction isolation level currently used for the stock‑decrement operation? Knowing this will determine whether the first step above is applicable.
SHOW ENGINE INNODB STATUS output.SELECT … FOR UPDATE wait times via performance_schema.events_waits_history_long.Use comments to ask for clarification. Post a solution as an answer.
32,275 reputation · 19 Feb 2021, 15:14 UTC
WooCommerce wraps the stock‑decrement in a transaction that begins at the woocommerce_checkout_process hook. The SELECT … FOR UPDATE lock is held until that transaction finishes, which can happen before the decrement is actually committed if the product isn’t re‑loaded. This means a second checkout can acquire the same lock after the first one has released it, allowing a brief window where stock can dip below zero—especially when backorders are enabled.
WooCommerce fires a woocommerce_deadlock action whenever InnoDB reports a deadlock, giving you a hook to log or retry.
To reduce contention:
innodb_lock_wait_timeout (e.g., to 5 s) but add retry logic.READ COMMITTED isolation if your database supports it.woocommerce_pre_process_checkout if you need the lock longer.