# Static vs Dynamic Data Masking: When to Use Which

> Static vs dynamic data masking — when each applies, how they differ on backups, performance, and reversibility, and when to run both.

Tianzhou | 2026-05-25 | Source: https://www.bytebase.com/blog/static-vs-dynamic-data-masking/

---

Static and dynamic data masking both protect sensitive columns, but they act at different moments. Static masking rewrites the data once, into a sanitized copy. Dynamic masking rewrites query results at read time and leaves the source untouched. Pick the wrong one and you either leak real PII into a test environment or fail to control who sees production data.

## Static data masking

Static data masking (SDM) produces a sanitized copy of a dataset. A job reads production, transforms the sensitive columns — substitute, shuffle, hash, nullify — and writes the result to a new dataset. The masking is baked into the data. There is no path back to the original values.

SDM exists to move data out of the production boundary safely. Dev, test, staging, demos, training, CI fixtures — anywhere the people using the data should never see real values. Because the copy holds no real PII, it can be distributed widely.

Two properties follow from masking the data itself:

- **The protection travels with the data.** A backup or replica of a masked dataset is also masked. Copy it anywhere; it stays safe.
- **The cost is paid once.** Masking runs at copy time. Queries against the copy run at full speed — no per-query overhead.

The trade-offs are structural too. The copy is stale the moment it is made; refreshing means re-running the job. And the masked output must preserve format and referential integrity, or the test data breaks — a masked SSN still has to look like an SSN, and foreign keys have to line up across masked tables.

## Dynamic data masking

Dynamic data masking (DDM) masks at read time. The stored data is unchanged. A policy on a column decides what each principal sees; the engine — or a layer in front of it — rewrites the result for anyone without the right role. See [What is Dynamic Data Masking](/blog/what-is-dynamic-data-masking/) for the full treatment.

DDM exists for production. The same live rows, different views per role — an analyst sees a partial mask, a DBA sees cleartext, a contractor sees a full mask. Grant the right role and the same query returns the real value. The data is always there; access decides what surfaces.

The same two properties invert:

- **The transform is query-time, not at rest.** A backup or replica holds the real values. Masking does not travel with the data — every read path has to enforce the policy, or that path leaks cleartext.
- **The cost is paid per query.** Masking runs on every `SELECT`. A heavy policy adds latency to each one.

The main risk follows: a privileged role or a direct connection that bypasses the masking layer sees cleartext. Coverage is only as complete as the read paths you enforce.

## Side by side

|                    | Static data masking                       | Dynamic data masking                          |
| ------------------ | ----------------------------------------- | --------------------------------------------- |
| When applied       | Once, at copy time                        | Every query, at read time                     |
| Data at rest       | Permanently altered (a copy)              | Unchanged                                     |
| Reversible         | No                                        | Yes — by role or grant                        |
| Environment        | Non-production (dev, test, staging)       | Production                                    |
| Backups & replicas | Masked — protection travels with the data | Cleartext — the transform is query-time       |
| Performance cost   | Paid once, at copy time                   | Paid on every query                           |
| Primary use        | Safe test data                            | Role-based access control                     |
| Main risk          | Stale data; broken referential integrity  | Bypass via privileged role or direct connection |

The row teams most often miss is backups. Static masking protects the copy; dynamic masking does not. If the compliance requirement is "no real PII in this dataset," dynamic masking alone does not meet it — the data at rest is still real.

## Tools and solutions

**Static masking** runs as a batch job, so the tools are test-data and data-pipeline platforms: Perforce Delphix, Informatica Persistent Data Masking, IBM InfoSphere Optim, Oracle Data Masking and Subsetting Pack, and Tonic.ai. See [Static Data Masking Tools](/blog/static-data-masking-tools/) for a closer look, including how to roll your own.

**Dynamic masking** runs at read time, so it lives in the engine or a layer in front of it:

- Native engine DDM: SQL Server, Oracle, Snowflake, and BigQuery ship it; MySQL and PostgreSQL do not. The per-engine guides below cover each.
- In front of the engine: Bytebase on the query path, and access-governance platforms such as Immuta and Satori.

## When to use which

- **Static** when data leaves production. Refresh a lower environment, hand a dataset to an offshore team, build CI fixtures, record a demo. The consumers should never hold real values.
- **Dynamic** when data stays in production and different users need different views of the same rows. Role-based access to live data.
- **Both** is the common end state. Refresh staging from production with static masking, and run dynamic masking on the production queries that remain. They solve different problems.

The canonical static workflow is the non-prod refresh: rebuild dev, test, or staging from production on a schedule, masking sensitive columns during the copy so no real PII lands downstream. Preserve formats and relationships so the data still exercises the code. For the production side, see [Dynamic Data Masking Best Practices](/blog/dynamic-data-masking-best-practices/).

In a regulated estate the two run side by side, from the same production database. Dynamic masking protects live production reads. A scheduled static-masking job feeds safe data to every non-production environment.

```mermaid
flowchart TB
    DB[("Production database<br/>real values at rest")]

    subgraph prod["In production — dynamic masking"]
        DDM["Masking at query time"]
        Human(["Analysts, support, on-call"])
        DDM --> Human
    end

    subgraph nonprod["Non-production — static masking"]
        Copy[("Masked copy<br/>no real PII")]
        Dev(["Developers, CI, demos"])
        Copy --> Dev
    end

    DB -->|"live queries"| DDM
    DB -->|"scheduled refresh<br/>masked on copy"| Copy

    style DDM fill:#e0f2fe,stroke:#0369a1
    style Copy fill:#e0f2fe,stroke:#0369a1
```

## Where Bytebase fits

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

Bytebase does dynamic masking, not static. [Bytebase Dynamic Data Masking](https://docs.bytebase.com/security/data-masking/overview/) governs the human query path: queries route through the SQL Editor, and results are masked before they leave it, by policy. One policy model applies across every engine in the fleet — including MySQL and PostgreSQL, which ship no native DDM.

Native DDM, where it exists, varies by engine: [Oracle](/blog/oracle-dynamic-data-masking/), [SQL Server](/blog/sql-server-dynamic-data-masking/), [BigQuery](/blog/bigquery-dynamic-data-masking/), and [Snowflake](/blog/snowflake-dynamic-data-masking-and-alternatives/) each have their own. [MySQL](/blog/mysql-dynamic-data-masking/) and [PostgreSQL](/blog/postgres-dynamic-data-masking/) do not. Bytebase applies the same policy across all of them.

---

Try Bytebase Dynamic Data Masking with [this tutorial](https://docs.bytebase.com/tutorials/data-masking/).