# Notes on the MySQL 9.7 LTS release

> Notes on the MySQL 9.7 LTS release: Hypergraph Optimizer in Community, JSON Duality View DML, Group Replication observability, and Dynamic Data Masking for Enterprise.

Tianzhou | 2026-05-12 | Source: https://www.bytebase.com/blog/notes-on-mysql-9-7-lts-release/

---

[MySQL 9.7](https://dev.mysql.com/doc/relnotes/mysql/9.7/en/news-9-7-0.html) shipped on April 21, 2026. It's the first LTS since 8.4, and the first release after Oracle's February pledge to re-engage with the MySQL community. The [release announcement](https://blogs.oracle.com/mysql/mysql-9-7-0-lts-is-now-available-expanded-community-capabilities-and-dynamic-data-masking-for-enterprise) covers everything that landed; below are the ones that caught my attention after going through the release notes.

## The Hypergraph Optimizer

MySQL's classical optimizer is a left-deep, greedy join enumerator. It plans fast and produces good plans on OLTP. The plan shapes it can produce are limited: every join extends a single left-deep chain, hash-vs-nested-loop is not a general cost-based choice, and interesting orders are not part of the search. On analytic queries with many joins, that gap shows up as orders-of-magnitude differences in execution time.

The Hypergraph Optimizer is the rewrite. It implements DPhyp from Moerkotte and Neumann's [_Dynamic Programming Strikes Back_](https://15721.courses.cs.cmu.edu/spring2020/papers/20-optimizer2/p539-moerkotte.pdf) (SIGMOD 2008): a cost-based, hypergraph-aware enumerator that considers bushy plans, picks hash vs nested-loop per join based on cost, and propagates interesting orders. It first surfaced as a developer preview in 8.0.23 and spent the 9.x Innovation cycle behind Enterprise. 9.7 is where it crosses into Community.

```sql
-- Session-level
SET optimizer_switch = 'hypergraph_optimizer=on';

-- Statement-level
SELECT /*+ SET_VAR(optimizer_switch = 'hypergraph_optimizer=on') */
  o.id, c.name, SUM(li.price)
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN line_items li ON li.order_id = o.id
WHERE o.created_at >= '2026-01-01'
GROUP BY o.id, c.name;
```

**Why it's useful:** A real planner for deep joins, star schemas, and analytical filters — without paying for Enterprise or moving to HeatWave. Per-statement opt-in via `SET_VAR` is the pragmatic pattern. Flipping it on globally is the wrong choice: the classical optimizer still wins on the OLTP fast path, and planning cost on simple queries is higher.

It is still experimental and off by default. On Oracle's published TPC-DS run, nearly half the queries improved by at least 25%, 19 improved by at least 50% — but 14 regressed by 50% or more. The wins outnumber the losses, but the losses are real. Refresh `ANALYZE TABLE` and your column histograms before flipping the switch; the hypergraph planner is far more sensitive to statistics quality than the classical one. And keep `SET_VAR` ready in both directions, in case a specific query is one of the losers.

## JSON Duality Views

JSON Duality Views let an application read and write a JSON document while the server stores the data as normalized relational rows. The 9.x Innovation track shipped them read-only in Community, read-write in Enterprise. 9.7 closes the gap: `INSERT`, `UPDATE`, and `DELETE` against a duality view are now supported in Community Server, with auto-increment propagation.

```sql
CREATE JSON DUALITY VIEW customer_orders AS
SELECT JSON_DUALITY_OBJECT{
  '_id': c.id,
  'name': c.name,
  'orders': [ SELECT JSON_DUALITY_OBJECT{
                '_id': o.id,
                'total': o.total_cents,
                'placed_at': o.created_at
              } FROM orders o WHERE o.customer_id = c.id ]
} FROM customers c;

-- Document-style write — server decomposes it into INSERT/UPDATE on base tables
INSERT INTO customer_orders VALUES (JSON_OBJECT(
  'name', 'Alice',
  'orders', JSON_ARRAY(JSON_OBJECT('total', 4200, 'placed_at', NOW()))
));
```

**Why it's useful:** Document and relational access from the same schema. A service that prefers documents reads and writes the view; an analytics pipeline reads the base tables directly. No ORM in the middle. Constraint violations on the base tables roll back the document write, which is the point — you want those errors loud, not papered over.

## Group Replication

Group Replication is already MySQL's clearest lead over Postgres: a multi-primary-capable, in-server HA topology with automatic failover, where Postgres still hands failover orchestration off to external tools. The catch was that running it well required Enterprise — the components that told you _why_ the group made a given decision lived only there. 9.7 closes that gap, which is what turns the lead from a paywalled one into a real one.

- **Replication Applier Metrics** — per-worker lag and throughput in multi-threaded replication.
- **Group Replication Flow Control Statistics** — how often, how long, and against which member flow control engaged.
- **Group Replication Resource Manager** — automatic eviction and rejoin of unhealthy members.
- **Group Replication Primary Election** — favors the most up-to-date eligible node during failover, not the lowest-UUID default.

**Why it's useful:** Your HA topology no longer has to track your support contract. Turn on applier metrics first — they're how you tell a hot worker apart from broader replication pressure. Resource manager and primary election actually change failover behavior, so roll those out against a staging cluster before production.

## OpenTelemetry Support

The Telemetry Component lands in Community. Previously Enterprise-only, it exports MySQL logs, metrics, and traces over OTLP. Configuration is component-based — installed and tuned like any other server component, not a separate plugin.

**Why it's useful:** MySQL is now on the same OTLP pipeline as everything else, which means MySQL spans land in the same dashboard as the application traces they're attached to. There's no longer a MySQL-shaped hole in centralized monitoring that needs a custom exporter to fill. One thing to plan for on busy clusters: the collector and the network path between it and the server have to handle the extra OTLP traffic.

## Dynamic Data Masking

Until 9.7, masking a sensitive column in MySQL meant one of three things: wrap the base table in a view, call an Enterprise masking function in every query that touched the column, or put a third-party gateway in front of the database. None of these survived a `SELECT *`, and none of them survived a developer pointing their own client at the read replica.

MySQL Enterprise 9.7 makes Dynamic Data Masking (DDM) a first-class object in the server. A masking policy decides, per row at query time, whether to return the original value or a transformed one, based on the executing user or active role. You attach the policy to a column on the base table, and every read path — `SELECT *`, `mysqldump`, ad-hoc client sessions — goes through it.

```sql
-- 1. Define a policy. The body is a CASE expression that returns either
--    the column value or a masked transformation of it.
CREATE MASKING POLICY mask_ssn AS (val VARCHAR(11))
RETURNS VARCHAR(11) ->
  CASE
    WHEN CURRENT_ROLE() IN ('compliance_auditor', 'pii_reader')
      THEN val
    ELSE mask_inner(val, 0, 4, 'X')
  END;

-- 2. Attach the policy to a column on the base table.
ALTER TABLE customers
  ALTER COLUMN ssn SET MASKING POLICY mask_ssn;

-- 3. Reads are filtered by the policy, transparently.
SELECT id, name, ssn FROM customers WHERE id = 42;
-- → 42 | Alice | XXXXX6789       (analyst role)
-- → 42 | Alice | 123-45-6789     (compliance_auditor role)
```

The policy body draws on Enterprise's pre-built masking library, which has shipped since 8.0 — `mask_inner`, `mask_outer`, `mask_pan`, `mask_ssn`, `gen_rnd_email`, `gen_rnd_us_phone`. The functions aren't new. What's new in 9.7 is the policy _object_: before, you could call these functions, but binding one to a column meant application code or a view. Now the server does that binding for you. The SQL surface looks a lot like [Snowflake's column-level DDM](https://docs.snowflake.com/en/user-guide/security-column-ddm-use) — same `CREATE MASKING POLICY ... RETURNS ... ->` shape, same `ALTER COLUMN ... SET MASKING POLICY` attachment — and not at all like Oracle's procedural `DBMS_REDACT.ADD_POLICY` API, which is the older sibling in the same family of products.

**Why it's useful:** Masking shifts from "every read path has to remember to call the function" to "the column is masked." A masked column is masked everywhere it appears, including inside `WHERE` and `JOIN` predicates, so the obvious side-channel — `WHERE ssn LIKE '123-%'` — is closed. One thing DDM doesn't do: it controls which roles see which columns in cleartext, but it doesn't control which rows a role can read at all. If both axes matter, pair it with row-level filtering.

## Re-earning the Trust

MySQL's community story drifted last year. Three-month stretches with no public commits. Layoffs inside the engineering team. On Hacker News, in conference hallways, in architecture reviews, the default answer became "use Postgres." Picking MySQL needed a defense.

That's changing. Oracle's February [New Era of MySQL Community Engagement](https://blogs.oracle.com/mysql/new-era-of-mysql-community-engagement) post made the promise; 9.7 keeps it. The optimizer rewrite ships in Community. Group Replication observability ships in Community. Dynamic Data Masking stays Enterprise — the right call, since the people buying masking are the same people buying the audit posture and support contract that come with it.

On operational merits, MySQL stands up against Postgres better than the consensus suggests:

- **Storage engine.** InnoDB is clustered-index and undo-log-based — old row versions live in the undo log, not the heap. Postgres's heap-plus-vacuum design is the [most-cited architectural pain point operators raise about Postgres](https://www.cs.cmu.edu/~pavlo/blog/2023/04/the-part-of-postgresql-we-hate-the-most.html). InnoDB tends to outperform heap+vacuum on OLTP workloads.
- **Replication and HA.** Group Replication ships in the server with automatic primary election and node eviction; MySQL Router and MySQL Shell finish the stack. Postgres has streaming replication in core but leaves failover orchestration to external tools.
- **Learning curve.** A new engineer is productive with MySQL in a day. Postgres asks more up front — roles, schemas, `search_path`, vacuum, autovacuum, TOAST, wraparound — and the first day is steeper.

## Further Reading

- [MySQL 9.7.0 release notes](https://dev.mysql.com/doc/relnotes/mysql/9.7/en/news-9-7-0.html)
- [Oracle: MySQL 9.7.0 LTS is now available](https://blogs.oracle.com/mysql/mysql-9-7-0-lts-is-now-available-expanded-community-capabilities-and-dynamic-data-masking-for-enterprise)
- [Oracle: The Hypergraph Optimizer is now available in MySQL 9.7 Community Edition](https://blogs.oracle.com/mysql/the-hypergraph-optimizer-is-now-available-in-mysql-9-7-community-edition)
- [Oracle: New Era of MySQL Community Engagement](https://blogs.oracle.com/mysql/new-era-of-mysql-community-engagement)