MariaDB 10.6 to 13.0 for WordPress: Only One Upgrade Actually Does Anything [Benchmark]

MariaDB 10.6 to 13.0 for WordPress: Only One Upgrade Actually Does Anything [Benchmark] - MakeWPFast

Someone read my March post about MariaDB 10.11 vs 11.4 and asked a fair question: they’re on 10.6.27, they never noticed any difference moving to 10.11, and a client wants to update. Is it worth it?

Short answer for that specific hop: no, you won’t feel it. But 10.6 hit end of life on July 6, 2026, so the update is worth doing anyway, and if you’re touching it at all you may as well go further than 10.11.

That question also made me realise my March post is out of date. My own host’s panel now offers 12.3.2 by default, with 13.0 sitting there as a release candidate. So I re-ran everything across six versions.

What I tested

MariaDB 10.6.28, 10.11.18, 11.4.12, 11.8.8, 12.3.2 and 13.0.1-RC. Same WordPress 7.1, same PHP 8.4.24, same WooCommerce 11.0.1, same data every time.

The dataset: 1,000 posts, 200 WooCommerce products, 5,000 comments, 101 users, 7,798 postmeta rows.

I rebuilt the test rig for this one. Three things I did differently to March, and the third one matters:

  • One database at a time. In March I ran both versions side by side on the same machine, which means they were fighting for CPU. This time each version gets the box to itself.
  • Identical data, not identically seeded data. I build the dataset once on 10.6, dump it, and import that same dump into every version. In March I ran the seeder twice, which gets you close but not identical.
  • Two full passes, second one in reverse order. 10.6 first then 13.0, then 13.0 first then 10.6. Anything that doesn’t show up in both passes is noise, and I’ll come back to how much noise there actually is.

Stock config throughout. No tuning, no buffer pool changes. I want to know what upgrading gives you, not what tuning gives you.

10.6 to 10.11: nothing

Every query, both passes, in milliseconds:

Query 10.6 10.11 Change
Autoloaded options 0.072 0.073 +2.8%
Single post + meta 0.158 0.161 +1.9%
Archive page (posts+meta+tax) 3.562 3.809 +6.9%
Correlated subquery 1.352 1.380 +2.0%
LIKE search 0.101 0.103 +2.5%
Aggregate (posts per category) 0.282 0.299 +6.0%
UPDATE with subquery 7.521 8.764 +16.5%
WooCommerce product listing 0.369 0.252 -31.8%
WooCommerce price filter 0.235 0.233 -0.6%
Bulk meta read (500 rows) 0.681 0.637 -6.5%
DATE() on indexed column 0.611 0.605 -1.1%

That column of percentages is what noise looks like. Half of it goes the wrong way. The single big number, WooCommerce product listing at -31.8%, is 0.12ms in absolute terms.

So the reader was right. They didn’t notice a difference because there isn’t one to notice.

The one thing 10.6 does consistently worse is homepage throughput. It served roughly 7-13% fewer requests per second than 10.11, depending on concurrency, and it came last in both passes at every level. That’s the only 10.6 result I’d call real, and it’s still small.

10.11 to 11.4: this is the whole story

Query 10.11 11.4 Change
UPDATE with subquery 8.764 0.140 -98.4%
DATE() on indexed column 0.605 0.087 -85.5%
Bulk meta read (500 rows) 0.637 0.454 -28.9%
Aggregate (posts per category) 0.299 0.243 -18.7%
Archive page (posts+meta+tax) 3.809 3.308 -13.2%
WooCommerce product listing 0.252 0.282 +12.1%
LIKE search 0.103 0.107 +3.4%

Same result I got in March, on a rebuilt rig with different data and a different WordPress. That’s reassuring, because a benchmark that only reproduces on the machine that produced it isn’t worth much.

Two optimizer changes do almost all of it. Semi-join for UPDATE and DELETE, so a subquery in a DML statement gets planned properly instead of being re-run per row. And sargable date functions, so WHERE YEAR(post_date) = 2025 can use the index instead of scanning the table.

The 40x has a mirror image

This is the part I got wrong in March, or at least the part I didn’t dig into.

That “40x faster UPDATE” test uses a subquery that matches zero rows. It measures how the optimizer plans the statement, which is a real thing that real cleanup crons hit, but it isn’t the only shape those queries come in.

So I wrote a second test that runs the same statement shape against subqueries that do match rows, 15 times each, inside a transaction that gets rolled back so every run starts from the same state. Medians in ms:

Test Rows matched 10.11 11.4 11.8 12.3 13.0-RC
A. UPDATE, subquery matches nothing 0 9.062 0.130 0.135 0.143 0.135
B. UPDATE postmeta by meta_key 999 1.124 1.011 0.964 1.077 0.958
C. DELETE postmeta by meta_key 999 2.665 2.753 2.502 3.007 2.471
D. UPDATE all meta of all products 1,800 4.453 8.629 9.434 9.535 9.355

Test A is the headline, and it’s bigger than I reported in March: 70x, not 40x.

Tests B and C are flat. When there’s a usable index on the table you’re updating, every version picks the same plan and runs at the same speed.

Test D is 11.4 running twice as slow as 10.11. And it stays twice as slow through 11.8, 12.3 and 13.0. I ran it twice on separate rebuilds and got the same thing both times.

EXPLAIN says why. On 10.11:

PRIMARY  wp_postmeta  index  key=PRIMARY  rows=7761  Using where
DEPENDENT SUBQUERY  wp_posts  unique_subquery  key=PRIMARY  rows=1

On 11.4 and everything after:

PRIMARY  wp_posts     ref  key=type_status_date  rows=200  Using where; Using index
PRIMARY  wp_postmeta  ref  key=post_id          rows=6

10.11 walks 7,761 postmeta rows in order and checks each one. 11.4 flips the driving table, finds the 200 products first, then does 200 separate index lookups into postmeta. Fewer rows examined, more random access, and on this dataset the sequential scan wins.

Worth being honest about the limits here: 7,798 postmeta rows is small and the whole thing sits in the buffer pool. On a table big enough that the sequential scan hits disk, the new plan probably wins instead. I haven’t tested that, so I’m not going to claim it.

The practical version: 4ms against 9ms doesn’t matter unless something runs it in a loop. I’d still take 11.4. But it’s a good reminder to distrust anyone quoting a single benchmark number, me in March included.

Everything after 11.4 is flat

This one I’m showing as both passes, pass 1 / pass 2, because the averages flatter these versions than the data supports:

Query 11.4 11.8 12.3 13.0-RC
Autoloaded options 0.081 / 0.077 0.075 / 0.076 0.078 / 0.043 0.079 / 0.075
Single post + meta 0.170 / 0.155 0.160 / 0.162 0.137 / 0.109 0.148 / 0.134
Archive page (posts+meta+tax) 3.603 / 3.014 2.941 / 2.921 3.098 / 3.558 2.923 / 2.861
UPDATE with subquery 0.140 / 0.139 0.143 / 0.149 0.130 / 0.133 0.137 / 0.134
Bulk meta read (500 rows) 0.454 / 0.453 0.439 / 0.443 0.430 / 0.452 0.408 / 0.364
DATE() on indexed column 0.093 / 0.082 0.088 / 0.084 0.087 / 0.088 0.083 / 0.028
WooCommerce price filter 0.242 / 0.229 0.226 / 0.218 0.216 / 0.228 0.225 / 0.160

Look at 12.3’s autoloaded options, 0.078 in one pass and 0.043 in the other. Or 13.0’s DATE() query, 0.083 then 0.028. Same container, same dump, an hour apart. On queries that finish in under a tenth of a millisecond, a single pass tells you almost nothing, and if I’d only run the matrix once I’d be writing “13.0 makes date queries 3x faster than 12.3” right now.

The archive page join is the one change I’d call real: about 11% faster on 11.8, and it holds in both passes and stays through 13.0. Everything else on that table is the machine breathing.

11.8 and 12.3 both got real work in the optimizer. Cost-based subquery strategies for UPDATE and DELETE in 11.8, index condition pushdown on reverse scans and loose index scan on descending keys in 12.3. None of it lands on the queries WordPress actually sends.

Page load and throughput: flat across all six

Page TTFB (ms) 10.6 10.11 11.4 11.8 12.3 13.0-RC
Homepage 53.1 52.1 51.1 51.6 54.0 52.7
Single post 21.9 21.3 22.6 21.3 23.6 21.9
Category archive 22.3 22.5 22.8 22.6 23.4 23.4
Shop page 43.1 43.2 43.2 43.5 44.1 45.5
REST posts 57.2 54.3 54.5 56.5 57.2 56.4

Before anyone reads a winner out of that table: when I benchmarked the same version twice on the same machine an hour apart, homepage TTFB moved by up to 11.6%. That’s my noise floor. Every difference in that table is below it.

In March I reported 4-10% faster page loads for 11.4 and presented it as a real gain. With a noise measurement in hand I don’t think that was justified. The SQL numbers were solid, the TTFB numbers were within drift.

Which makes sense once you look at where the time goes. A 3.3ms query inside a 52ms response is 6% of the total. Cut it in half and you save 1.6ms. PHP is doing the rest of the work.

The “4x write performance” claim

MariaDB’s own 12.3 announcement leads with 4x write performance. I tested the WordPress write path directly through wp eval-file, so no HTTP round trip in the numbers. Milliseconds:

Operation 10.6 10.11 11.4 11.8 12.3 13.0-RC
wp_insert_post + 4 meta rows 4.382 4.028 4.591 4.346 4.524 4.412
update_post_meta 0.288 0.282 0.287 0.278 0.315 0.262
wp_update_post 3.144 3.123 3.078 3.248 3.129 2.883

Nothing. Whatever workload that 4x describes, publishing a post isn’t it.

One correction while I’m here: the write test in my March post was broken. It created posts by POSTing to /wp-json/wp/v2/posts with curl --user admin:password. WordPress core rejects HTTP Basic auth, so that loop was timing rejected requests, not inserts. The “2.5% faster on writes” line in that post measured nothing. This version runs through WP-CLI instead.

What actually changed that can break you

Two defaults moved, and both are more likely to bite you than any of the performance numbers above.

innodb_snapshot_isolation is ON from 11.8 onwards. It was OFF in 10.6, 10.11 and 11.4. Under concurrent writes to the same row you can now get “Record has changed since last read” errors instead of silent last-write-wins. That’s correct behaviour and it’s why they changed it, but a WooCommerce store with a busy checkout is exactly where you’d meet it. Test on staging with real concurrency, not with one browser tab.

The default collation changed to utf8mb4_uca1400_ai_ci in 11.4. Your existing tables are fine, WordPress sets its own collation when it creates them. New tables created by plugins that don’t specify one will pick up the new default, and joining those against a utf8mb4_unicode_520_ci core table gives you “Illegal mix of collations”. I didn’t hit it in this test, but I’ve seen it in the wild.

Nothing else broke. WordPress 7.1, WooCommerce 11.0.1 and PHP 8.4 ran clean on all six versions including the 13.0 release candidate.

So what should you do

On 10.6: update. Not for speed, for support. Community support ended July 6, 2026, so you’re running an unpatched database. Going to 10.11 buys you until February 2028 and changes nothing else. Going to 11.4 or 11.8 buys you real query improvements and support into 2028-2029.

On 10.11: this is the upgrade that pays. 11.4 or 11.8, whichever your host offers. If they offer both, take 11.8, it’s supported to June 2028 and the archive query is a bit quicker.

On 11.4 or newer: stay. There’s nothing in 11.8, 12.3 or 13.0 that a WordPress site will feel. Upgrade when your support window gets close, not for performance.

Thinking about 13.0: don’t, it’s a release candidate. It performed fine, which is what I’d expect, but a release candidate under your client’s store is a bad trade for zero measurable gain.

And if the hosting panel offers 12.3 as the default, take it. It’s an LTS, supported to June 2029, and it’s not slower. Just don’t expect the upgrade itself to speed anything up.

The thing I’d actually do with an afternoon, if the goal is a faster site: object caching and a look at what your slowest plugin queries are doing. The database version stopped being the bottleneck at 11.4.


How I tested: one Docker stack at a time on an Apple Silicon Mac, 6 CPUs and 8GB allocated to the VM. WordPress 7.1, PHP 8.4.24, WooCommerce 11.0.1, stock MariaDB config. The dataset is built once on 10.6, dumped, and imported into each version, with ANALYZE TABLE run on every table after import so optimizer statistics are deterministic. SQL tests: 25 runs each, median. TTFB: 20 runs per page, median. Throughput: Apache Bench, 200 requests at concurrency 1, 5, 10 and 25. Writes: 200 inserts, 500 meta updates and 200 post updates through wp eval-file. Every number above is from two full passes, the second run in reverse version order. Where the two passes disagreed by more than a few percent I’ve said so.

Get WordPress Performance Tips

Join developers and agency owners who get backend optimization strategies, tool releases, and deep-dive guides.

No spam. Unsubscribe anytime. I respect your privacy.