
MySQL 9.7.0 LTS landed today, and we had the pleasure of watching the announcement live — and we were the first ones to download. If you've already pulled the binaries and you're poking at it in a lab, here's the news: Readyset already supports it. The point isn't that Readyset happens to work with 9.7. The point is that we were prepared for the launch. Our compatibility matrix has been running 9.7 pre-release builds, so the same readysettech/readyset:latest image you can docker pull right now
Vinicius Grippa
2026-04-22 · 7 min read
MySQL 9.7.0 LTS landed today, and we had the pleasure of watching the announcement live — and we were the first ones to download. If you've already pulled the binaries and you're poking at it in a lab, here's the news: Readyset already supports it.
The point isn't that Readyset happens to work with 9.7. The point is that we were prepared for the launch. Our compatibility matrix has been running 9.7 pre-release builds, so the same readysettech/readyset:latest image you can docker pull right now already accepts 9.7 as an upstream. Today's job is to prove it, not to port it.
rdst stores connection profiles in ~/.rdst/config.toml. One non-interactive command adds the target, and the add-with-verify path handshakes against the server to confirm it's reachable:
$ pipx install rdst
$ export MYSQL97_PASSWORD=readyset
$ rdst configure add \
--target mysql97 \
--engine mysql \
--host mysql97.example.com \
--port 3306 \
--user readyset \
--database demo \
--password-env MYSQL97_PASSWORD \
--confirm
Expected output:
╭────────────────────────────── Connection Test ──────────────────────────────╮
│ ✅ Connected successfully! │
│ Server: MySQL 9.7.0 │
╰─────────────────────────────────────────────────────────────────────────────╯
╭────────────────────── Target 'mysql97' ─────────────────────────────────────╮
│ ✅ has been added. │
╰─────────────────────────────────────────────────────────────────────────────╯
That "Server: MySQL 9.7.0" string is the quiet proof: the rdst MySQL driver handshakes cleanly against a 9.7 GA server, reads the version, and moves on. No protocol-version surprises, no "unsupported server" warnings.
One MySQL 9.7 gotcha that bites everyone, not just Readyset. The mysql_native_password plugin is gone from 9.7 — you can't even INSTALL PLUGIN it back in, because the shared object no longer exposes the symbol the server expects. Create the Readyset user with caching_sha2_password (the default since 8.0). Readyset has supported that for years, so there's nothing to change on our side:
CREATE USER IF NOT EXISTS 'readyset'@'%'
IDENTIFIED WITH caching_sha2_password BY 'readyset';
GRANT ALL ON *.* TO 'readyset'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
If you have service accounts still sitting on the old plugin, plan that migration into the upgrade.
This is the command that would have taken half a page in a hand-rolled guide. Now it's one line:
$ rdst cache deploy --target mysql97 --mode docker --port 3307
🚀 Deploying Readyset for target 'mysql97' (docker)...
Preparing deployment...
Deploying ReadySet (docker)...
Pulling and starting ReadySet container (this may take a while)...
Registering cache target...
╭────────────────────────── Deploy Complete ──────────────────────────────────╮
│ ReadySet deployed successfully │
│ │
│ ✅ Connection endpoint: │
│ mysql://readyset@127.0.0.1:3307/demo │
│ │
│ Cache target: mysql97-cache │
│ Container: rdst-readyset-mysql97 │
╰─────────────────────────────────────────────────────────────────────────────╯
Under the hood, rdst pulled readysettech/readyset:latest, wired UPSTREAM_DB_URL to the target we just registered and exposed port 3307 for SQL, and registered a second target — mysql97-cache — that points at the freshly-started Readyset.
The Readyset container's startup log shows it connecting to 9.7 on the first try — no version negotiation drama:
$ docker logs rdst-readyset-mysql97 2>&1 | tail
readyset: Spawning HTTP request server task
readyset_adapter::http_router: Adapter listening for HTTP connections addr=0.0.0.0:6034
readyset: Query logs are enabled. Spawning query logger
readyset_server::http_router: Server listening for HTTP connections addr=127.0.0.1:6033
readyset_server::controller: won leader election, creating Leader
readyset_server::controller::inner: received registration payload from worker
invalidation_sidecar:schema_catalog_invalidate: schema_catalog::handle:
Invalidating all cached query state due to schema change
From rdst's point of view there are now two live targets, both verified:
$ rdst configure list
Database Targets
╭─────────────────────┬────────┬──────────────────────┬──────────┐
│ Target │ Engine │ Connection │ Verified │
├─────────────────────┼────────┼──────────────────────┼──────────┤
│ mysql97 │ MySQL │ mysql97.example.com:3306 │ Yes │
│ mysql97-cache │ MySQL │ 127.0.0.1:3307/demo │ Yes │
╰─────────────────────┴────────┴──────────────────────┴──────────╯
$ rdst configure test mysql97-cache
╭──────────── Connection Test: mysql97-cache ────────────╮
│ ✅ Connection successful (Server: MySQL 9.7.0) │
╰───────────────────────────────────────────────────────────────╯
Readyset advertises itself as MySQL 9.7.0 because that's the upstream it's fronting — the version string is passed through verbatim, so any client that inspects VERSION() or @@version sees the real thing, not some proxy-identity placeholder.
rdst query cache-compare against real 9.7.0 GABefore benchmarking, I grew the orders table to ~200k rows (the seed schema is tiny on purpose, so there's nothing to cache). Then the command I actually wanted to run:
$ rdst query cache-compare \
"SELECT c.name, COUNT(o.id) AS orders, SUM(o.total) AS spent
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'delivered'
GROUP BY c.id, c.name ORDER BY spent DESC" \
--target mysql97 --count 100 --skip-warning
Query registered (hash: f7e239c0)
Preparing cache for comparison...
Running 100 iterations against upstream (mysql97)...
Running 100 iterations against cache (mysql97-cache)...
Result:
| Metric | Upstream (MySQL 9.7.0) | Cache (local Readyset) | Improvement |
|---|---|---|---|
| Min | 50.92 ms | 1.11 ms | 45.9x |
| Avg | 63.18 ms | 1.56 ms | 40.5x |
| P50 | 59.45 ms | 1.39 ms | 42.8x |
| P95 | 95.84 ms | 2.77 ms | 34.6x |
| P99 | 119.76 ms | 3.63 ms | 33.0x |
| Max | 119.76 ms | 3.63 ms | 33.0x |
63 ms → 1.56 ms average. Upstream MySQL 9.7.0, scanning ~200k rows to build a per-customer aggregate, lands around 60–100 ms per iteration. The same query through Readyset — running as a Docker container on my laptop — lands in under 2 ms on average, with P99 at 3.63 ms (still comfortably below the upstream minimum of 50.9 ms). Every iteration after the first miss is served from the cache.
A caveat worth stating. Caching earns its keep when there's real work to skip. On a trivial point-lookup against a handful of rows, the cache overhead can outweigh the savings. That's why the demo table has 200k rows, and why the "cache beats upstream" story above is honest rather than synthetic.
cache-compare creates a temporary cache for the duration of the test and cleans up after itself. Make it permanent with one more command:
$ rdst cache add \
"SELECT c.name, COUNT(o.id) AS orders, SUM(o.total) AS spent
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'delivered'
GROUP BY c.id, c.name ORDER BY spent DESC" \
--target mysql97-cache
╭────────────────── Cache Created ──────────────────╮
│ Shallow cache created successfully │
│ Hash: f7e239c0ec88 │
╰───────────────────────────────────────────────────╯
$ rdst cache show --target mysql97-cache
╭──────────┬────────────────────┬──────────────────────────┬─────────┬──────╮
│ Hash │ Cache Name │ Query │ Type │ TTL │
├──────────┼────────────────────┼──────────────────────────┼─────────┼──────┤
│ f7e239c0 │ q_fc381ca24cf972e5 │ SELECT c.name, COUNT(...)│ shallow │ 600s │
╰──────────┴────────────────────┴──────────────────────────┴─────────┴──────╯
rdst analyze rounds it out — it queries MySQL 9.7 to look at the plan and checks Readyset for an existing cache of the same query:
$ rdst analyze -q "SELECT c.name, COUNT(o.id) AS orders, SUM(o.total) AS spent
FROM customers c JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'delivered'
GROUP BY c.id, c.name ORDER BY spent DESC" --target mysql97
╭────────────────────── 🚀 Readyset Performance ──────────────────────╮
│ Status: CACHEABLE ✓ │
│ Confidence: high │
│ Method: readyset_shallow │
│ │
│ CACHE PERFORMANCE │
│ Cached query time: 0.60ms │
│ │
│ Explanation: Query is already cached in Readyset │
│ (query_id: q_fc381ca24cf972e5) │
╰─────────────────────────────────────────────────────────────────────╯
For copy-paste:
# 0. one-time - macOS
pipx install rdst
# 1. register MySQL 9.7 as a target
export MYSQL97_PASSWORD=readyset
rdst configure add --target mysql97 --engine mysql \
--host mysql97.example.com --port 3306 --user readyset --database demo \
--password-env MYSQL97_PASSWORD --confirm
# 2. deploy Readyset locally in Docker, pointed at that target
rdst cache deploy --target mysql97 --mode docker --port 3307
# 3. see the speedup
rdst query cache-compare "<your SQL here>" --target mysql97 --count 100
Three steps from "nothing installed" to "sub-2-ms cached reads off a real 9.7.0 GA upstream."
readyset.conf.Every future MySQL release gets the same treatment: pre-GA builds on our compatibility runners, the Docker image updated before the announcement, and the rdst flow above working the hour the release hits. The goal is for this post to stay accurate, just with a different version number each time.
Modern applications demand instant performance, even under unpredictable load. Readyset helps you eliminate slow queries, stabilize latency, and scale confidently.
Revolutionize your database performance with Readyset
Serve requests at sub-millisecond latencies with the modern database scaling and query caching system for MySQL and PostgreSQL.
Join our newsletter
Stay updated with the latest news, insights, and developments from Readyset — straight to your inbox.