# MySQL Dynamic Data Masking

> Compare MySQL data masking options — first-class DDM in 9.7 Enterprise, the legacy masking plugin, Percona's OSS variant, and Bytebase fleet-wide masking.

Tianzhou | 2026-05-14 | Source: https://www.bytebase.com/blog/mysql-dynamic-data-masking/

---

Sensitive columns — SSNs, credit cards, emails, addresses — must stay queryable for support, analytics, and development. Broad cleartext access is not the answer. Data masking is.

For years, MySQL gave you two paths: a function library in Enterprise, an OSS clone from Percona — both wired through views. [MySQL 9.7 LTS](/blog/notes-on-mysql-9-7-lts-release/) (April 21, 2026) adds a third: a first-class masking policy object inside the server. Four options in total. This post compares them.

## MySQL 9.7 Enterprise Dynamic Data Masking

[Dynamic Data Masking](https://blogs.oracle.com/mysql/introducing-dynamic-data-masking-in-mysql-protect-sensitive-data-without-app-changes) (DDM) is GA in **MySQL 9.7 Enterprise Edition** and **OCI MySQL HeatWave**. Define a policy. Attach it to a column. The server enforces it.

```sql
-- 1. Define the policy. The gatekeeper decides who sees cleartext.
CREATE MASKING POLICY mask_ssn_policy(ssn_col)
  CASE WHEN CURRENT_USER_IN('seeall')
       THEN ssn_col
       ELSE mask_ssn(ssn_col)
  END;

-- 2. Attach the policy to the column.
ALTER TABLE protected.user_profiles
  ALTER COLUMN ssn
  SET MASKING POLICY mask_ssn_policy;

-- 3. Every read goes through the policy.
SELECT id, first_name, last_name, ssn FROM protected.user_profiles;
-- noseeall → XXX-XX-0001   (masked)
-- seeall   → 900-01-0001   (cleartext)
```

Two gatekeepers ship in 9.7: `CURRENT_USER_IN(...)` and `CURRENT_ROLE_IN(...)`. The policy body uses the existing MySQL masking function library — `mask_inner`, `mask_outer`, `mask_pan`, `mask_ssn`, `gen_rnd_email`, `gen_rnd_us_phone`. The functions are not new. The policy _object_ is. Before 9.7, binding a masking function to a column meant a view or an in-query call. In 9.7, the server does the binding.

What the policy object adds:

- **Server-side enforcement.** Interactive SQL, application traffic, `mysqldump`, downstream extracts — every read path returns the masked value. Direct connections to the base table cannot bypass it.
- **No view inventory.** One policy replaces a per-variation view catalog.
- **Predicate safety.** Masking applies inside `WHERE` and `JOIN`. The `WHERE ssn LIKE '900-%'` side-channel is closed.

What it does not do:

- **Row-level filtering.** DDM gates columns, not rows. For row-level control, pair with row-level filtering.
- **Cover Community or Percona.** Enterprise and HeatWave only.

## MySQL Enterprise Data Masking Plugin (legacy)

Before 9.7, the only Enterprise path was the [data masking plugin](https://dev.mysql.com/doc/refman/8.0/en/data-masking-plugin-usage.html), shipped since 5.7. The plugin exposes masking functions (`mask_inner`, `mask_outer`, `mask_ssn`, …). It does not expose a policy object. You protect raw data by wrapping the base table in a view that calls those functions, then granting access to the view.

![_](/content/blog/mysql-dynamic-data-masking/mysql.webp)

The pattern still works on 8.0 and 8.4. Its limits are well understood:

- **Account model first.** Enforcement keys off MySQL roles. Most instances have a handful of users. Adopting the plugin means redesigning accounts before policies.
- **One view per variation.** The view inventory grows with every variation. Schema changes ripple through it.
- **No management surface.** It is plain SQL. Version, review, and audit it yourself.
- **Base-table bypass.** Anyone with `SELECT` on the base table sees cleartext.

On 9.7 Enterprise, the policy object replaces this pattern for new work. The functions remain for direct calls.

## Percona Data Masking Plugin

[Percona Data Masking](https://docs.percona.com/percona-server/8.0/data-masking-plugin-functions.html) is the open-source clone of the legacy Enterprise plugin, shipped with Percona Server for MySQL.

![_](/content/blog/mysql-dynamic-data-masking/percona.webp)

Same function library. Same view-based pattern. Same limits. Free, but only on Percona Server. As of 9.7, Percona has not implemented `CREATE MASKING POLICY`. The OSS path is still views over functions.

## Bytebase Dynamic Data Masking

![_](/content/blog/mysql-dynamic-data-masking/bb-masking-overview.webp)

[Bytebase Dynamic Data Masking](https://docs.bytebase.com/security/data-masking/overview/) defines policies in Bytebase and enforces them at the SQL Editor query result. No views. No per-engine plugin. One policy model across every MySQL distribution. Policy changes and exemption requests run through a built-in workflow — **Request. Review. Approve.** — with every step audited.

Policies are composed from three layers, evaluated in fixed precedence: **Masking Exemption > Global Masking Rule > Column Masking**.

1. **Global Masking Rule.** Workspace-level, ordered like iptables — the first matching rule wins. Match conditions span environment, project, database, and data classification level. Each match applies a Semantic Type, which in turn selects a masking algorithm (full, partial, MD5, range, or custom).

![_](/content/blog/mysql-dynamic-data-masking/bb-global-masking.webp)

2. **Column Masking.** Project-level override. `Project Owner` assigns a Semantic Type to a specific column when the global rule does not apply. Global rules still take precedence over column masking.

![_](/content/blog/mysql-dynamic-data-masking/bb-column-masking.webp)

3. **Masking Exemption.** Named users receive time-bound `Query` or `Export` exemptions to specific databases or tables. Service accounts are not eligible — exemptions are for human users querying through the SQL Editor. Every grant and every access is logged.

![_](/content/blog/mysql-dynamic-data-masking/bb-grant-exemption.webp)

Masking is **infectious**: when a column is masked, the policy propagates to every view and derived structure that depends on it. The result reaches the SQL Editor directly:

![_](/content/blog/mysql-dynamic-data-masking/bb-sql-editor-full-masking.webp)

_Policies can also be codified via [GitOps](https://github.com/bytebase/example-database-security)._

Masking decisions are recorded in the [audit log](/blog/bytebase-audit-logging/#what-bytebase-records). Every SQL execution entry carries per-column masking metadata — which columns came back masked, which Semantic Type triggered it, and which rule matched — alongside the user, source IP, statement, and row count. Granted exemptions, used exemptions, and policy edits are first-class audit events. The policy and the proof it was enforced live in the same record.

Enforcement boundary: Bytebase masks queries routed through the SQL Editor. Traffic that hits the database directly bypasses it. The operational pattern is to funnel human access through Bytebase — at which point masking applies uniformly across MySQL Community, Enterprise, Percona, RDS, Aurora, and managed forks.

## Comparison

|                  | MySQL 9.7 Enterprise DDM             | Enterprise Plugin (legacy)    | Percona Plugin              | Bytebase Dynamic Data Masking                   |
| ---------------- | ------------------------------------ | ----------------------------- | --------------------------- | ----------------------------------------------- |
| Compatibility    | MySQL 9.7+ Enterprise, HeatWave      | MySQL Enterprise              | Percona Server for MySQL    | All MySQL distributions ⭐️                     |
| Mechanism        | Policy object on column ⭐️          | Functions + views             | Functions + views           | Policy in Bytebase, applied at SQL Editor       |
| Enforced at      | Database, every read path ⭐️        | View boundary                 | View boundary               | SQL Editor                                      |
| Policy mgmt      | First-class server objects           | DIY views                     | DIY views                   | Centralized UI, grants, audit log ⭐️           |
| Workflow         | DDL only                             | DDL only                      | DDL only                    | Request. Review. Approve. ⭐️                   |
| Row-level filter | No (pair with row-level filtering)   | No                            | No                          | No (pair with access policy)                    |
| Price            | Paid                                 | Paid                          | Free ⭐️                    | Paid                                            |

## Picking one

- **On 9.7 Enterprise or OCI HeatWave.** Use the policy object. Only option that enforces uniformly across `SELECT *`, `mysqldump`, and direct client sessions.
- **On 8.0 or 8.4 Enterprise.** Use the legacy plugin with views. Plan the migration to policies on the next LTS upgrade.
- **On Percona Server, no Enterprise budget.** Use the Percona plugin. Same constraints as the legacy Enterprise plugin.
- **Mixed MySQL fleet, or MySQL alongside Postgres, Snowflake, and managed forks.** Use Bytebase. One policy model. Every engine. Audited grants for every unmask.

---

Try Bytebase Dynamic Data Masking with [this tutorial](https://docs.bytebase.com/tutorials/data-masking/). Questions? Join our [Discord](https://discord.com/invite/huyw7gRsyA).