Skip to main content

MySQL Dynamic Data Masking

Tianzhou · May 14, 2026

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 (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 (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.

-- 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, 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.

_

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 is the open-source clone of the legacy Enterprise plugin, shipped with Percona Server for MySQL.

_

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

_

Bytebase Dynamic Data Masking 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).

_

  1. 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.

_

  1. 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.

_

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:

_

Policies can also be codified via GitOps.

Masking decisions are recorded in the audit log. 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 DDMEnterprise Plugin (legacy)Percona PluginBytebase Dynamic Data Masking
CompatibilityMySQL 9.7+ Enterprise, HeatWaveMySQL EnterprisePercona Server for MySQLAll MySQL distributions ⭐️
MechanismPolicy object on column ⭐️Functions + viewsFunctions + viewsPolicy in Bytebase, applied at SQL Editor
Enforced atDatabase, every read path ⭐️View boundaryView boundarySQL Editor
Policy mgmtFirst-class server objectsDIY viewsDIY viewsCentralized UI, grants, audit log ⭐️
WorkflowDDL onlyDDL onlyDDL onlyRequest. Review. Approve. ⭐️
Row-level filterNo (pair with row-level filtering)NoNoNo (pair with access policy)
PricePaidPaidFree ⭐️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. Questions? Join our Discord.

Back to blog

Explore the standard for database development