What Is a Learned Query Optimizer? Why PostgreSQL Picks Slow Plans and How to Steer It
A query optimizer turns your SQL into a plan: which table to read first, which index to use, how to join one table to the next. A learned query optimizer is one that improves its choices from what actually happened when queries ran, instead of trusting its own predictions. The idea is older than it sounds, and it’s finally practical.
To see why it matters, look at how an ordinary optimizer goes wrong.
Plans Are Built on Guesses
PostgreSQL’s planner can’t run every possible plan to see which is fastest. It estimates. For each step it guesses how many rows will come out, from statistics it keeps on each column, then prices every plan with those guesses and picks the cheapest.

The guesses are good for one column at a time. They break when columns are related. Say every customer on the basic tier also signed up on the web. The statistics know each fact alone. They don’t know the two go together, so the planner multiplies the odds as if they were independent and lands far too low. Extended statistics can fix some of these cases, if someone knows to create them. Values inside a JSON column are worse: the planner has no statistics there at all and guesses that any value matches half a percent of rows.
That’s what happens in LQO’s demo. The planner expects about five basic web customers. There are 149,978. A plan built for five customers looks up each one’s orders through an index, and each order’s lines through another. Perfect for five. For 149,978 it means about 900,000 separate index lookups, and the query takes 1,547 ms. The same query with nested loops switched off reads the tables once and joins them in memory, in 894 ms.
The planner didn’t make a bad choice. It made a good choice for the wrong number.
Learning From What Ran
A learned optimizer closes that loop. It watches what happens when queries run, notices where the predictions were wrong, and plans differently next time.
IBM did a version of this for DB2 in 2001, correcting its estimates from what queries returned. Research since 2019 went further and trained neural networks to build plans from scratch. They worked in the lab and struggled in production: slow to train, hard to explain, and prone to the occasional disastrous plan.
The turning point was a 2021 paper from MIT called Bao. Its insight was to stop replacing the optimizer and start steering it. PostgreSQL already has switches that rule out a join or scan method, such as enable_nestloop = off. Bao learns which switches to flip for which query, and leaves the planning to PostgreSQL. Every plan it can produce is one PostgreSQL already knows how to run, and you can always ask why: because this switch was off.
That’s the approach the big platforms took to production. Microsoft steers the optimizer of its SCOPE data platform this way. Databricks published a history-based optimizer in 2026 that cut join latency by a quarter on the queries it covers, and Alibaba reported up to 30% less CPU from a learned optimizer on its MaxCompute warehouse. All of them are in-house. The ordinary PostgreSQL user couldn’t get one.
Where LQO Fits
LQO brings the steering idea to any PostgreSQL. It keeps the learning simple and fully explainable. It doesn’t train a model. It runs each kind of query under the default plan and under each switch, on your data, on a replica, and measures. A switch is suggested only when it was never much slower on any value tried, clearly faster overall, and returned the same rows.
That makes LQO less clever than a research system and easier to trust. Every suggestion comes with the timings that earned it. On the Join Order Benchmark, the standard test for optimizers, it found a better switch for 35 of 94 kinds of query and left the other 59 alone. A suggestion you can’t explain doesn’t belong on a production database, and a slow query you know how to fix is worth more than a fast one you don’t understand.
The quick start has LQO running on a demo database in a couple of minutes, traps included.