A WooCommerce store can feel slow while a database cleanup guide for plain WordPress finds almost nothing to clean. The reason is that most of the weight on a store sits in tables those guides never mention: the one holding customer sessions, the ones used by the background job queue, and wherever your order data lives. Cleaning those correctly is a different job from clearing revisions and spam comments.
This guide goes through those three places in the order they are worth checking, with a safe way to change each one. Take a database backup from your host before you start, and try anything destructive on a staging copy first if you have one.
Where a WooCommerce database actually gets heavy
Check sizes before touching anything. With WP-CLI:
wp db size --tables --size_format=mb
Without shell access, phpMyAdmin shows the same list in the database’s structure view. Sort by size and look for these names, using your own table prefix in place of wp_:
wp_woocommerce_sessions, which holds customer cart sessions.wp_actionscheduler_actionsandwp_actionscheduler_logs, which belong to the background task queue WooCommerce and many of its extensions depend on.wp_postmeta, if your orders are still stored the old way, or the fourwc_orderstables if they are not.
Whichever of these is largest tells you where to start. If none of them is, the weight is probably in autoloaded options, which a store can suffer from just as much as any other site.
The sessions table: safe to trim, if you do it the right way
WooCommerce stores each visitor’s cart and related temporary data in wp_woocommerce_sessions. Every row has a session_expiry timestamp, and a session lasts 48 hours by default. Rows past that timestamp are dead weight.
There are two ways to clear this table, and they are not equivalent. WooCommerce has a button for it under WooCommerce, Status, Tools called Clear customer sessions. It removes every session, including the ones belonging to shoppers who are in the middle of checking out right now, and the screen warns that active carts will be lost. On a quiet site at 3 a.m. that is acceptable. On a store with steady traffic it means some customers find their cart empty.
The safer version deletes only what has already expired:
DELETE FROM wp_woocommerce_sessions WHERE session_expiry < UNIX_TIMESTAMP();
Run it through wp db query or phpMyAdmin. Live carts are untouched. If the table was large, follow it with OPTIMIZE TABLE wp_woocommerce_sessions; to give the freed space back. If it fills up again within days, the real fix is further upstream, such as bot traffic creating a session on every request, and a caching or firewall rule that stops bots from reaching cart pages will do more than repeated cleaning.
Action Scheduler: the table most guides skip
Action Scheduler is the queue WooCommerce uses for work that should not happen while a customer waits: sending scheduled emails, processing subscription renewals, syncing data with outside services, running the HPOS migration. Each job is a row in wp_actionscheduler_actions, and every status change is a row in wp_actionscheduler_logs. On a busy store those two tables can reach many gigabytes.
The queue cleans up after itself, but only partly. By default it removes completed and canceled actions older than 31 days after each run. Failed actions were not removed at all, so they piled up indefinitely. That is changing: the WooCommerce developer blog describes Action Scheduler 4.0.0, bundled with WooCommerce 11.0, as purging failed actions once they are older than three months, through a daily cleanup at 3 a.m. site time. If your store is on an older version, the failed rows are still there.
Look at the failed actions before deleting them
A failed action is a record of something that did not happen: an email that was never sent, a payment webhook that was not processed, a sync that stopped. Open WooCommerce, Status, Scheduled Actions and filter by Failed. If the same hook name appears thousands of times, one broken extension is producing them, and deleting the rows hides the problem without fixing it. Fix or remove the extension first, then clean.
Cleaning with WP-CLI
Action Scheduler ships a clean command. Its documented options include --status, --batch-size (default 20), --batches, --before (default 31 days) and --pause for a delay between batches:
wp action-scheduler clean --status=failed --batch-size=200 --pause=1
Run wp action-scheduler clean --help on your own install to confirm the exact flags and the format --before expects, since they can differ between versions. Working in batches with a pause keeps the database from being hammered while the store is live.
Shortening how long finished actions are kept
If the tables keep growing because the store runs a lot of jobs, lower the retention period with a filter, added in a small code snippet or your child theme’s functions file:
add_filter( 'action_scheduler_retention_period', function () {
return WEEK_IN_SECONDS;
} );
That keeps completed and canceled actions for seven days instead of 31. A week of history is plenty for debugging most problems. If you rely on a longer audit trail for subscriptions or scheduled shipping, leave the default alone.
When a table is already huge
If a table is several gigabytes, batches of 200 may take hours. Some guides truncate the log table outright, which is quick but permanent. The log table holds only history, so it is the less risky of the two, but still take a backup first and confirm nothing in the actions table is pending that you need.
Order data: check where it lives before anyone edits it
WooCommerce can keep orders in two ways. The old way stores each order as a post, with its details as rows in wp_postmeta. High-Performance Order Storage, usually called HPOS, uses four dedicated tables: wc_orders, wc_order_addresses, wc_order_operational_data and wc_orders_meta. It has been the default for new stores since WooCommerce 8.2.
Find out which one you are on under WooCommerce, Settings, Advanced, Features. The answer decides how you handle order-related rows:
- On HPOS: order data is already out of
wp_postmeta, so a bloated postmeta table is a plugin problem and our guide to cleaning wp_postmeta safely applies in full. - Not on HPOS: every order is a post with a pile of meta rows, and a bulk delete of “old” meta can destroy order history. Do not hand-clean those rows. Moving the store to HPOS is a migration with its own compatibility checks, not a cleanup step, so plan it separately.
If you do not need old orders for accounting, export them first. Deleting orders is the one cleanup here you cannot undo from the database alone.
What an optimize button will and will not do
After large deletions, running OPTIMIZE TABLE on the affected tables reclaims disk space and defragments them. It does not speed up a store whose real problem is a slow query, an uncached cart page or an overloaded server. Deleting rows only helps when the rows were the bottleneck, which is why measuring first matters more than any single command.
A sensible order of work
- Back up the database and note table sizes.
- Delete expired rows from the sessions table, not the whole table.
- Look at failed Action Scheduler actions, fix what is producing them, then clean in batches.
- Shorten the retention period only if the tables grow back quickly.
- Confirm whether orders are on HPOS before touching any order-related rows.
- Check table sizes again and run
OPTIMIZE TABLEon the ones that shrank.
Three of these tables account for most of a store’s database weight, and each has a way of being cleaned that is safe and a way that is not. The difference is nearly always whether you looked at what the rows were before you deleted them.

Etienne Basson works with website systems, SEO-driven site architecture, and technical implementation. He writes practical guides on building, structuring, and optimizing websites for long-term growth.