Dynamic data masking lets you control what sensitive data specific users see—without changing their SQL queries or the underlying data. When a user queries a table, MaxCompute intercepts the results and applies masking in real time based on the user's identity and role.
When to use dynamic data masking
Dynamic data masking is useful when different users need different levels of access to the same columns. For example:
A customer support agent can confirm the last four digits of a phone number without seeing the full number.
A developer can run queries against production data for troubleshooting without exposing personally identifiable information (PII).
A data analyst can work with salary ranges rather than exact figures.
Masking applies consistently across queries, downloads, joins, and user-defined function (UDF) computations.
How it compares to other access controls
| Control method | How it works | SQL query changes required | Best for |
|---|---|---|---|
| Dynamic data masking | Masks column values at query time based on user/role | No | Showing different data views to different users |
| Access Control List (ACL) column-level permissions | Blocks access to entire columns | Yes — users must rewrite queries to exclude restricted columns | Preventing access to sensitive columns entirely |
| Row-level permissions | Filters rows based on user/role | No | Restricting which rows a user can see |
Dynamic data masking and row-level permissions are mutually exclusive for the same table. You cannot apply both to a single table.
How it works
Project owners and users with the Super_Administrator or Admin role manage masking policies. When a user queries a table:
MaxCompute checks which masking policies apply to the user's identity and roles.
For each sensitive column, the applicable policy (or the highest-priority policy if multiple apply) determines what the user sees.
The system returns either masked or plaintext data—without modifying the underlying data.

Masking is applied at the point closest to data retrieval from storage, which provides high performance and security.

Limitations
Availability: This feature is in public preview and is available in the following regions: China (Hangzhou), China (Shanghai), China (Beijing), China (Zhangjiakou), China (Ulanqab), China (Shenzhen), China (Chengdu), China (Hong Kong), Japan (Tokyo), Singapore, Malaysia (Kuala Lumpur), Indonesia (Jakarta), Germany (Frankfurt), and US (Silicon Valley).
Supported connection methods:
| Connection method | Minimum version |
|---|---|
| Java SDK | 0.48.0-public |
| odpscmd | 0.47.1 |
| JDBC | 3.4.3 |
| MaxFrame | Unlimited |
| PyODPS | Unlimited |
| Go SDK | Unlimited |
Tables and views:
Masking policies apply to internal tables only, not foreign tables.
For traditional views, masking policies are synchronized with the source table. Attaching or detaching a policy from the source table also affects the traditional view.
For materialized views, the source table's masking policy is applied at creation time. Subsequent policy changes on the source table do not affect the materialized view.
If you mask Chinese characters, the column encoding must be UTF-8.
Enable or disable masking for a project
Only a project owner or a user with the Super_Administrator or Admin role can enable or disable the odps.data.masking.policy.enable project property.
-- Enable dynamic data masking for the project
setproject odps.data.masking.policy.enable=true;
-- Disable dynamic data masking for the project
setproject odps.data.masking.policy.enable=false;Create a masking policy
A masking policy defines who sees masked data and which masking method to use.
CREATE DATA MASKING POLICY [IF NOT EXISTS] <policy_name>
TO { USER <user_list> | ROLE <role_list> | DEFAULT }
USING <predefined_masking_policy>;To delete a policy:
DROP DATA MASKING POLICY <policy_name>;Parameters:
| Parameter | Required | Description |
|---|---|---|
policy_name | Yes | The policy name. Case-insensitive. Can contain letters, digits, and underscores (_). Start with a letter. Maximum 128 bytes. |
USER | Yes (pick one) | Masks data for specific users. Run list users; to get usernames. |
ROLE | Yes (pick one) | Masks data for specific roles. Run list roles; to get role names. |
DEFAULT | Yes (pick one) | Fallback policy. Applied when no user- or role-specific policy matches. |
predefined_masking_policy | Yes | The masking method to apply. See Predefined masking policies. |
Examples:
-- Apply MD5 hashing to data seen by userA, userB, and userC
CREATE DATA MASKING POLICY IF NOT EXISTS masking_test_001
TO USER (userA, userB, userC)
USING MASKED_MD5(0);
-- Apply MD5 hashing to data seen by the developer and O&M roles
CREATE DATA MASKING POLICY IF NOT EXISTS masking_test_001
TO ROLE (role_project_deploy, role_project_dev)
USING MASKED_MD5(0);Apply a policy to a column
Bind or unbind masking policies to specific columns of a table.
-- Bind a policy to a column
APPLY DATA MASKING POLICY <policy_name> BIND TO TABLE <table_name> COLUMN <column_name>;
-- Unbind a specific policy from a column
APPLY DATA MASKING POLICY <policy_name> UNBIND FROM TABLE <table_name> COLUMN <column_name>;
-- Unbind all policies from a column
APPLY DATA MASKING POLICY UNBIND ALL FROM TABLE <table_name> COLUMN <column_name>;
-- Unbind all policies from all columns of a table
APPLY DATA MASKING POLICY UNBIND ALL FROM TABLE <table_name>;Parameters:
| Parameter | Required | Description |
|---|---|---|
policy_name | Yes | The name of the masking policy. |
table_name | Yes | The table containing the sensitive column. |
column_name | Yes | The sensitive column to mask. |
View masking policies
-- View details of a specific policy
DESC DATA MASKING POLICY <policy_name>;
-- View extended table info, including sensitive columns and applied policies
DESC EXTENDED <table_name>;
-- List all policies in the current project
LIST DATA MASKING POLICY;
-- List policies applied to a user
LIST DATA MASKING POLICY TO USER <user_name>;
-- List policies applied to a role
LIST DATA MASKING POLICY TO ROLE <role_name>;
-- List all policies on a table
LIST DATA MASKING POLICY ON <table_name>;
-- List all policies on a specific column
LIST DATA MASKING POLICY ON <table_name> TO COLUMN <column_name>;Predefined masking policies
Choose a masking method based on your data type and sensitivity requirements.
| Category | Policy | Command | Supported types | Description |
|---|---|---|---|---|
| No masking | UNMASKED | UNMASKED | All | Returns data as-is. Use this to explicitly grant plaintext access. |
| Nullify | MASKED_NULLIFY | MASKED_NULLIFY | All | Returns NULL. |
| Default value | MASKED_DV | MASKED_DV | All | Replaces data with the type's default value (0, "", false, epoch timestamps, etc.). |
| Date truncation | MASKED_DATE_YEAR | MASKED_DATE_YEAR | DATE, DATETIME, TIMESTAMP_NTZ, TIMESTAMP | Keeps only the year; sets month, day, and time to January 1, 00:00 (UTC). |
| Numeric rounding | MASKED_POINT_RESERVE | MASKED_POINT_RESERVE(<num>) | DECIMAL, FLOAT, DOUBLE | Rounds to num decimal places (0–5). |
| Mask start and end | MASKED_STRING_MASKED_BA | MASKED_STRING_MASKED_BA(<before>, <after>) | STRING, VARCHAR, CHAR, BINARY | Replaces the first before and last after characters with *. |
| Mask middle | MASKED_STRING_UNMASKED_BA | MASKED_STRING_UNMASKED_BA(<before>, <after>) | STRING, VARCHAR, CHAR, BINARY | Keeps the first before and last after characters in plaintext; masks the middle with *. |
| SHA256 hashing | MASKED_SHA256 | MASKED_SHA256(<salt>) | STRING, VARCHAR, CHAR, BINARY | Hashes data with SHA256. salt is an integer from 0–9. |
| SHA512 hashing | MASKED_SHA512 | MASKED_SHA512(<salt>) | STRING, VARCHAR, CHAR, BINARY | Hashes data with SHA512. salt is an integer from 0–9. |
| MD5 hashing | MASKED_MD5 | MASKED_MD5(<salt>) | STRING, VARCHAR, CHAR, BINARY | Hashes data with MD5. salt is an integer from 0–9. |
| SM3 hashing | MASKED_SM3 | MASKED_SM3(<salt>) | STRING, VARCHAR, CHAR, BINARY | Hashes data with SM3. salt is an integer from 0–9. |
| Random replacement | MASKED_REPLACE_RANDOM | MASKED_REPLACE_RANDOM(<position>) | STRING, VARCHAR, CHAR, BINARY | Replaces characters with random digits and letters. position=0: all; position>0: first N; position<0: last N. |
| Random replacement at start and end | MASKED_REPLACE_RANDOM_BA | MASKED_REPLACE_RANDOM_BA(<before>, <after>) | STRING, VARCHAR, CHAR, BINARY | Replaces the first before and last after characters with random digits and letters. |
| Fixed replacement | MASKED_REPLACE_FIXED | MASKED_REPLACE_FIXED(<position>, <fixed_string>) | STRING, VARCHAR, CHAR, BINARY | Replaces characters with a fixed string (≤100 chars, no spaces). position=0: all; position>0: first N; position<0: last N. |
Policy examples
MASKED_NULLIFY — set to null:
-- Input (string)
| col_string |
| Michael Johnson |
-- After masking
| col_string |
| NULL |MASKED_DV — assign default value:
-- Input (timestamp), project time zone: UTC+8
| col_timestamp |
| 2024-05-01 11:12:13 |
-- After masking
| col_timestamp |
| 1970-01-01 08:00:00 |MASKED_DATE_YEAR — keep only year:
-- Input (timestamp), project time zone: UTC+8
| col_timestamp |
| 2024-05-01 11:12:13 |
-- After masking
| col_timestamp |
| 2024-01-01 08:00:00 |MASKED_POINT_RESERVE(2) — round to 2 decimal places:
-- Input (float)
| col_float |
| 1.12345 |
-- After masking
| col_float |
| 1.12 |MASKED_STRING_MASKED_BA(3, 3) — mask start and end:
-- Input (string)
| col_string |
| Michael Johnson |
-- After masking: first 3 and last 3 characters replaced with *
| col_string |
| ***hael John*** |MASKED_STRING_UNMASKED_BA(1, 1) — mask middle:
-- Input (string)
| col_string |
| Michael Johnson |
-- After masking: only first and last characters kept in plaintext
| col_string |
| M*************n |MASKED_SHA256(0) — SHA256 hash:
-- Input (string)
| col_string |
| 4562-1234-5678-9123 |
-- After masking
| col_string |
| zwGMB1aCF1t705EfcwdDorql4MZb46XBqQJw/2RVx8U= |MASKED_SHA512(0) — SHA512 hash:
-- Input (string)
| col_string |
| 4562-1234-5678-9123 |
-- After masking
| col_string |
| 3PPywfEIp08WuTUI8FZCCfdVuRu68wZTVwWWVAf4pboACUnH6w9kFMLpl2AARaGW/mvWvg26p0EIqmE0fAEiuA== |MASKED_MD5(0) — MD5 hash:
-- Input (string)
| col_string |
| 4562-1234-5678-9123 |
-- After masking
| col_string |
| mK/o08tew5g7S3XV/BkFfw== |MASKED_SM3(0) — SM3 hash:
-- Input (string)
| col_string |
| 4562-1234-5678-9123 |
-- After masking
| col_string |
| Q2TfwUh4B8QQH8jPL6DfdoGysx/CXBxn2T14dDwQtQw= |MASKED_REPLACE_RANDOM(7) — replace first 7 characters randomly:
-- Input (string)
| col_string |
| Michael Johnson |
-- After masking
| col_string |
| 4DlJQxi Johnson |MASKED_REPLACE_RANDOM_BA(4, 4) — replace start and end randomly:
-- Input (string)
| col_string |
| Michael Johnson |
-- After masking: first 4 and last 4 characters replaced randomly
| col_string |
| r0xEael JohnWNr |MASKED_REPLACE_FIXED(7, "Oli") — replace first 7 characters with fixed string:
-- Input (string)
| col_string |
| Michael Johnson |
-- After masking
| col_string |
| Oli Johnson |Policy priority
When a user matches multiple masking policies on the same column, the policy with the highest priority (lowest priority number) takes effect.
For example, if a user matches both MASKED_REPLACE_RANDOM(3) (priority 3) and MASKED_SM3 (priority 4), MASKED_REPLACE_RANDOM(3) applies.
| Priority | Policy |
|---|---|
| 0 (highest) | UNMASKED |
| 1 | MASKED_POINT_RESERVE |
| 2 | MASKED_DATE_YEAR |
| 3 | MASKED_STRING_MASKED_BA, MASKED_STRING_UNMASKED_BA, MASKED_REPLACE_RANDOM, MASKED_REPLACE_RANDOM_BA, MASKED_REPLACE_FIXED |
| 4 | MASKED_SHA256, MASKED_SHA512, MASKED_MD5, MASKED_SM3 |
| 5 | MASKED_DV |
| 6 (lowest) | MASKED_NULLIFY |
Examples
Mask personal information columns
This example masks several columns in a personal information table for a specific user, while hashing phone numbers for all other users by default.
Step 1: Prepare the table and data.
-- Create the table
CREATE TABLE IF NOT EXISTS personal_info (
id bigint COMMENT 'The unique ID of the user.',
name string COMMENT 'The name of the user.',
age int COMMENT 'The age of the user.',
gender string COMMENT 'The gender of the user.',
height float COMMENT 'The height of the user.',
birthday date COMMENT 'The birth date of the user.',
phone_number string COMMENT 'The phone number of the user.',
email string COMMENT 'The email address of the user.',
address string COMMENT 'The address of the user.',
salary decimal(18, 2) COMMENT 'The salary of the user.',
create_time timestamp COMMENT 'The time when the user information was created.',
update_time timestamp COMMENT 'The time when the user information was updated.',
is_deleted boolean COMMENT 'A flag that indicates whether the user information is deleted.'
);
-- Insert sample data
INSERT INTO personal_info VALUES
(1, 'Zhang San', 18, 'Male', 178.56, '1990-01-01', '13800000000', 'zhangsan@example.com', 'Haidian District, Beijing', 5000.00, '2023-04-19 11:32:00', '2023-04-19 11:32:00', false),
(2, 'Li Si', 20, 'Female', 162.70, '1992-02-02', '13900000000', 'lisi@example.com', 'Pudong New Area, Shanghai', 6000.00, '2023-04-19 11:32:00', '2023-04-19 11:32:00', false),
(3, 'Wang Wu', 22, 'Male', 185.21, '1994-03-03', '14000000000', 'wangwu@example.com', 'Nanshan District, Shenzhen', 7000.00, '2023-04-19 11:32:00', '2023-04-19 11:32:00', false);Step 2: Create and apply masking policies.
-- name: keep only the first character, mask the rest with *
CREATE DATA MASKING POLICY IF NOT EXISTS masking_name
TO USER (RAM$xxx@test.aliyunid.com:xxx)
USING MASKED_STRING_UNMASKED_BA(1, 0);
APPLY DATA MASKING POLICY masking_name BIND TO
TABLE personal_info COLUMN name;
-- height: round to 0 decimal places
CREATE DATA MASKING POLICY IF NOT EXISTS masking_height
TO USER (RAM$xxx@test.aliyunid.com:xxx)
USING MASKED_POINT_RESERVE(0);
APPLY DATA MASKING POLICY masking_height BIND TO
TABLE personal_info COLUMN height;
-- birthday: keep only the year
CREATE DATA MASKING POLICY IF NOT EXISTS masking_birthday
TO USER (RAM$xxx@test.aliyunid.com:xxx)
USING MASKED_DATE_YEAR;
APPLY DATA MASKING POLICY masking_birthday BIND TO
TABLE personal_info COLUMN birthday;
-- phone_number: SM3 hash for all other users by default
CREATE DATA MASKING POLICY default_sm3
TO DEFAULT
USING MASKED_SM3(0);
APPLY DATA MASKING POLICY default_sm3 BIND TO
TABLE personal_info COLUMN phone_number;Step 3: Query as the masked user.
SELECT id, name, height, birthday, phone_number FROM personal_info;Before masking:
+----+-----------+--------+------------+--------------+
| id | name | height | birthday | phone_number |
+----+-----------+--------+------------+--------------+
| 1 | Zhang San | 178.56 | 1990-01-01 | 13800000000 |
| 2 | Li Si | 162.7 | 1992-02-02 | 13900000000 |
| 3 | Wang Wu | 185.21 | 1994-03-03 | 14000000000 |
+----+-----------+--------+------------+--------------+After masking:
+----+--------+--------+------------+----------------------------------------------+
| id | name | height | birthday | phone_number |
+----+--------+--------+------------+----------------------------------------------+
| 1 | Zhang* | 179.0 | 1990-01-01 | lvYJaH4ElL2ilpQx/8tfMUw7xP22yblIgmfWp0/msUQ= |
| 2 | Li* | 163.0 | 1992-01-01 | 9fFWacNSwCRZLAjMHqunlfwkqhTbP2ubuDOeOSh4N1c= |
| 3 | Wang* | 185.0 | 1994-01-01 | k/0JoQCSarJg9ATJ5tyVnhQf1jIBxHXRbB+cvUm4OmE= |
+----+--------+--------+------------+----------------------------------------------+Grant plaintext access to privileged users
Use UNMASKED to let specific users bypass masking while all other users see hashed data.
-- All users see SHA256-hashed data by default
CREATE DATA MASKING POLICY default_hash_policy
TO DEFAULT
USING MASKED_SHA256(5);
-- Users A and B see plaintext data
CREATE DATA MASKING POLICY ab_unmask_policy
TO USER (A, B)
USING UNMASKED;Users A and B match both policies. Because UNMASKED has priority 0 (the highest), they see plaintext. All other users see SHA256-hashed data.
When a user matches multiple policies on the same column, the policy with the highest priority takes effect. For the full priority order, see Policy priority.
Appendix: Default values for MASKED_DV
| Data type | Default value |
|---|---|
| bigint | 0 |
| double | 0.0 |
| decimal | 0 |
| string | "" |
| datetime | DATETIME'1970-01-01 00:00:00' (UTC) |
| boolean | false |
| tinyint | 0 |
| smallint | 0 |
| int | 0 |
| binary | '' |
| float | 0.0 |
| varchar(n) | "" |
| char(n) | " " (n spaces) |
| date | DATE'1970-01-01' |
| timestamp | TIMESTAMP'1970-01-01 00:00:00' (UTC) |
| timestamp_ntz | TIMESTAMP'1970-01-01 00:00:00' (UTC) |
| array | {Default value of the child class} |
| map | {key:value} (key and value correspond to the default values of the child class) |
| json | "" |
| struct | (Default value of the child class) |