Loading learning content...
A database without security is a database waiting to be breached. As data has become the most valuable asset of modern organizations, the systems that protect access to that data have become critical infrastructure. Every unauthorized data access, every privilege escalation, every insider threat—these are the adversaries that Data Control Language (DCL) is designed to defend against.
DCL is the security layer of SQL. While DDL defines what objects exist and DML manipulates their contents, DCL determines who can do what to those objects. Every database user, every application connection, every administrative action is governed by permissions defined through DCL statements.
In an era of data breaches, regulatory compliance (GDPR, HIPAA, SOX, PCI-DSS), and zero-trust security models, DCL is not optional—it is fundamental. A database administrator who cannot properly configure access control is a liability; a developer who ignores security principles invites catastrophe.
By the end of this page, you will deeply understand DCL's two primary statements—GRANT and REVOKE—their precise semantics, the types of privileges they control, and how they interact with database users, roles, and objects. You'll learn role-based access control patterns, the principle of least privilege, and real-world security implementation strategies.
Data Control Language (DCL) is the subset of SQL that provides commands for controlling access to database objects. DCL manages the security model of the database—determining which users can perform which operations on which objects.
The Two Fundamental DCL Commands:
DCL consists of just two primary statements:
| Statement | Purpose | Security Effect |
|---|---|---|
| GRANT | Give permissions to users or roles | Expands what a user can do |
| REVOKE | Remove permissions from users or roles | Restricts what a user can do |
The DCL Security Model:
DCL implements a discretionary access control (DAC) model. In DAC:
This contrasts with mandatory access control (MAC) where a central authority strictly controls all access. Most relational databases use DAC, giving object owners significant control over their objects' security.
The foundational security principle for DCL is 'least privilege': every user should have exactly the permissions required to perform their job function—no more, no less. Start with zero permissions and grant only what's necessary. This limits the damage from compromised accounts and reduces the attack surface of your database.
Privileges in database systems are categorized into multiple types, each controlling different aspects of database access. Understanding this hierarchy is essential for effective security design.
| Category | Scope | Examples | Typical Recipients |
|---|---|---|---|
| System Privileges | Database-wide operations | CREATE TABLE, CREATE USER, BACKUP DATABASE | DBAs, privileged roles |
| Object Privileges | Specific database objects | SELECT on table X, EXECUTE on procedure Y | Application users, developers |
| Column Privileges | Specific columns within tables | SELECT(salary) on employees | Limited access roles |
| Schema Privileges | All objects within a schema | ALL PRIVILEGES ON SCHEMA sales | Schema owners, team leads |
Common Object Privileges:
Different database objects support different privilege types:
| Object Type | Available Privileges | Description |
|---|---|---|
| TABLE | SELECT, INSERT, UPDATE, DELETE, REFERENCES, TRUNCATE | Full CRUD and referential access |
| VIEW | SELECT, INSERT, UPDATE, DELETE | Access through the view (if updateable) |
| SEQUENCE | USAGE, SELECT | Generate next value, query current value |
| FUNCTION/PROCEDURE | EXECUTE | Call the function or procedure |
| SCHEMA | CREATE, USAGE | Create objects in schema, access schema objects |
| DATABASE | CONNECT, CREATE, TEMPORARY | Connect to database, create schemas, create temp tables |
123456789101112131415161718192021222324252627282930
-- =====================================================-- Privilege Hierarchy Examples-- ===================================================== -- SYSTEM PRIVILEGES (database-wide)-- These control the ability to perform administrative operationsGRANT CREATE TABLE TO developer_role;GRANT CREATE USER TO admin_role;GRANT BACKUP DATABASE TO backup_operator; -- OBJECT PRIVILEGES (specific table)-- These control access to individual database objectsGRANT SELECT ON employees TO reporting_user;GRANT INSERT, UPDATE ON orders TO order_entry_app;GRANT ALL PRIVILEGES ON products TO inventory_manager; -- COLUMN-LEVEL PRIVILEGES (specific columns)-- Fine-grained control within a tableGRANT SELECT (employee_id, first_name, last_name, department_id) ON employees TO directory_app;-- Notice: salary, SSN, and other sensitive columns NOT granted GRANT UPDATE (email, phone) ON employees TO self_service_app;-- Users can update their own contact info, but not salary -- SCHEMA PRIVILEGES (all objects in schema)-- Useful for team-based accessGRANT USAGE ON SCHEMA sales TO sales_team;GRANT CREATE ON SCHEMA sales TO sales_developers;GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA sales TO sales_admin;GRANT ALL PRIVILEGES gives complete access to an object. While convenient, it violates the principle of least privilege. If a user only needs SELECT and INSERT, granting ALL also gives them DELETE and UPDATE—permissions they shouldn't have. Always enumerate specific privileges unless ALL is genuinely required.
The GRANT statement assigns privileges to database principals (users, roles, or groups). It is the primary mechanism for enabling access to database resources.
GRANT Syntax and Semantics:
The general form of GRANT is:
GRANT <privileges> ON <object> TO <principal> [WITH GRANT OPTION];
The GRANT executes immediately and takes effect for the principal's next database action. Existing sessions may not immediately see new privileges (depending on caching).
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
-- =====================================================-- GRANT Statement: Complete Examples-- ===================================================== -- 1. Grant single privilege to single userGRANT SELECT ON customers TO analyst_user; -- 2. Grant multiple privileges to single userGRANT SELECT, INSERT, UPDATE ON orders TO order_service; -- 3. Grant to multiple users at onceGRANT SELECT ON reports TO user1, user2, user3; -- 4. Grant to PUBLIC (all users - use sparingly!)GRANT SELECT ON public_announcements TO PUBLIC; -- 5. Grant all privileges (owner-level access)GRANT ALL PRIVILEGES ON inventory TO warehouse_manager; -- 6. Grant with ability to further grant (delegation)GRANT SELECT ON products TO team_lead WITH GRANT OPTION;-- team_lead can now: GRANT SELECT ON products TO team_member; -- 7. Grant on all tables in schema (PostgreSQL)GRANT SELECT ON ALL TABLES IN SCHEMA reporting TO report_readers; -- 8. Grant on future tables (PostgreSQL default privileges)ALTER DEFAULT PRIVILEGES IN SCHEMA sales GRANT SELECT ON TABLES TO sales_analysts;-- New tables created in 'sales' schema will auto-grant SELECT -- 9. Grant column-level privilegesGRANT SELECT (id, name, department) ON employees TO directory_service;GRANT UPDATE (email, phone) ON employees TO self_service_portal; -- 10. Grant EXECUTE on functions/proceduresGRANT EXECUTE ON FUNCTION calculate_tax(numeric) TO billing_app;GRANT EXECUTE ON PROCEDURE process_order(integer) TO order_processor; -- 11. Grant schema privilegesGRANT USAGE ON SCHEMA confidential TO cleared_users;GRANT CREATE ON SCHEMA development TO dev_team; -- 12. Grant database-level privileges (PostgreSQL)GRANT CONNECT ON DATABASE production TO app_user;GRANT CREATE ON DATABASE staging TO developer; -- 13. Grant sequence privilegesGRANT USAGE ON SEQUENCE order_id_seq TO order_service; -- 14. System privileges (Oracle-style)-- GRANT CREATE TABLE TO developer_role;-- GRANT CREATE SESSION TO app_user;-- GRANT DBA TO admin_user;WITH GRANT OPTION: Privilege Delegation
The WITH GRANT OPTION clause is powerful and dangerous. It allows the recipient to grant the same privilege to other users. This creates a chain of privilege delegation:
Owner → User A (WITH GRANT OPTION) → User B (WITH GRANT OPTION) → User C
Each user in the chain can grant to others. If User A's privilege is later revoked, what happens to Users B and C depends on the REVOKE syntax (CASCADE vs RESTRICT).
Instead of granting privileges directly to users, grant to roles, then assign users to roles. This simplifies administration: when an employee changes teams, change their role membership rather than modifying dozens of individual grants. It also enables consistent policy enforcement across user populations.
The REVOKE statement removes privileges from database principals. It is the mechanism for restricting access when users change roles, leave the organization, or when permissions need to be tightened.
REVOKE Syntax and Semantics:
The general form of REVOKE is:
REVOKE <privileges> ON <object> FROM <principal> [CASCADE | RESTRICT];
REVOKE takes effect immediately for new operations. Existing sessions may continue using cached privileges until they reconnect or refresh their permission cache.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
-- =====================================================-- REVOKE Statement: Complete Examples-- ===================================================== -- 1. Revoke single privilege from single userREVOKE SELECT ON customers FROM former_employee; -- 2. Revoke multiple privilegesREVOKE INSERT, UPDATE, DELETE ON orders FROM demoted_user; -- 3. Revoke all privilegesREVOKE ALL PRIVILEGES ON sensitive_data FROM compromised_account; -- 4. Revoke from multiple usersREVOKE SELECT ON reports FROM user1, user2, user3; -- 5. Revoke from PUBLIC -- 6. Revoke GRANT OPTION (but keep the privilege)REVOKE GRANT OPTION FOR SELECT ON products FROM team_lead;-- team_lead can still SELECT, but cannot grant to others -- 7. CASCADE revoke: Remove from grantees of the revokeeREVOKE SELECT ON data FROM manager CASCADE;-- If manager granted to subordinates, their access is also revoked -- 8. RESTRICT revoke: Fail if others have dependent grantsREVOKE SELECT ON data FROM manager RESTRICT;-- Fails if manager granted to others (must revoke them first) -- 9. Revoke schema privilegesREVOKE USAGE ON SCHEMA confidential FROM former_cleared_user;REVOKE CREATE ON SCHEMA development FROM former_developer; -- 10. Revoke column privilegesREVOKE UPDATE (salary) ON employees FROM basic_hr_role; -- 11. Revoke on all tables in schema (PostgreSQL)REVOKE SELECT ON ALL TABLES IN SCHEMA finance FROM departing_employee; -- 12. Revoke EXECUTE on functionsREVOKE EXECUTE ON FUNCTION sensitive_calculation() FROM untrusted_user; -- 13. Complete access removal workflow-- Step 1: Revoke object privilegesREVOKE ALL ON ALL TABLES IN SCHEMA public FROM terminated_user;REVOKE ALL ON ALL SEQUENCES IN SCHEMA public FROM terminated_user;REVOKE ALL ON ALL FUNCTIONS IN SCHEMA public FROM terminated_user; -- Step 2: Revoke schema accessREVOKE USAGE ON SCHEMA public FROM terminated_user; -- Step 3: Revoke database connectionREVOKE CONNECT ON DATABASE production FROM terminated_user; -- Step 4: Disable/drop the user account-- ALTER USER terminated_user WITH NOLOGIN;-- or: DROP USER terminated_user;CASCADE vs RESTRICT Semantics:
When a user with GRANT OPTION has granted privileges to others, REVOKE behavior depends on the CASCADE/RESTRICT keyword:
CASCADE: The privilege is revoked from the specified user AND from everyone they granted it to. This can cause a chain of revocations.
RESTRICT: The REVOKE fails if the user has granted the privilege to anyone else. You must explicitly revoke from the downstream users first.
CASCADE is powerful but can have unintended consequences. In complex grant chains, a single CASCADE REVOKE can disable access for many users.
When users are terminated, it's critical to revoke all their privileges before removing their account. Otherwise, if the username is reused, the new user may inherit the old user's permissions. Always follow a complete privilege removal workflow: revoke object privileges, schema access, and database connection before disabling or dropping the user account.
Role-Based Access Control (RBAC) is the industry-standard approach to managing database permissions at scale. Instead of granting privileges directly to users, privileges are granted to roles, and users are assigned to roles.
The RBAC Model:
Privileges → Roles → Users
[SELECT on orders] → [OrderReader] → [analyst1, analyst2, analyst3]
[INSERT on orders] → [OrderWriter] → [app_service, order_entry_user]
[ALL on orders] → [OrderAdmin] → [order_manager]
This indirection provides enormous administrative benefits:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
-- =====================================================-- Role-Based Access Control Implementation-- ===================================================== -- 1. CREATE ROLES for different access levelsCREATE ROLE readonly;CREATE ROLE readwrite;CREATE ROLE admin; -- 2. GRANT privileges TO roles (not directly to users)-- Read-only role: SELECT on all relevant tablesGRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;GRANT USAGE ON SCHEMA public TO readonly; -- Read-write role: SELECT + INSERT + UPDATE + DELETEGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO readwrite;GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO readwrite;GRANT USAGE ON SCHEMA public TO readwrite; -- Admin role: All privileges + DDLGRANT ALL PRIVILEGES ON SCHEMA public TO admin;GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO admin;GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO admin; -- 3. ROLE INHERITANCE: Admin includes readwrite includes readonlyGRANT readonly TO readwrite;GRANT readwrite TO admin;-- Now: admin has readonly + readwrite + admin privileges -- 4. CREATE users and ASSIGN to rolesCREATE USER analyst1 WITH PASSWORD 'secure_password_1';CREATE USER analyst2 WITH PASSWORD 'secure_password_2';CREATE USER app_service WITH PASSWORD 'app_service_key';CREATE USER dba_user WITH PASSWORD 'admin_secure_pass'; GRANT readonly TO analyst1;GRANT readonly TO analyst2;GRANT readwrite TO app_service;GRANT admin TO dba_user; -- 5. REVOKE role membership when user changes roleREVOKE readonly FROM analyst1;GRANT readwrite TO analyst1; -- Promoted! -- 6. Application-specific rolesCREATE ROLE order_processing;GRANT SELECT, INSERT ON orders TO order_processing;GRANT SELECT, INSERT ON order_items TO order_processing;GRANT USAGE ON SEQUENCE order_id_seq TO order_processing;GRANT EXECUTE ON FUNCTION calculate_order_total(integer) TO order_processing; CREATE ROLE payment_processing;GRANT SELECT, INSERT, UPDATE ON payments TO payment_processing;GRANT SELECT ON orders TO payment_processing;GRANT EXECUTE ON FUNCTION process_payment(integer, numeric) TO payment_processing; -- 7. Default privileges for future objects (PostgreSQL)ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO readwrite; -- 8. View role memberships-- PostgreSQL: SELECT * FROM pg_roles;-- PostgreSQL: \du (in psql)Most organizations implement a tiered role structure: (1) readonly — SELECT only for reporting/analytics. (2) readwrite — Full DML for application services. (3) developer — readwrite + DDL on development schemas. (4) dba_operator — System monitoring, backup/restore. (5) dba_admin — Full administrative access. Start with this foundation and customize for your specific needs.
Effective database security extends beyond DCL syntax to encompass design patterns, operational practices, and security architecture. The following best practices represent industry standards for securing database access.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
-- =====================================================-- Security Implementation Patterns-- ===================================================== -- 1. Application-specific user with minimal privilegesCREATE USER order_app WITH PASSWORD 'randomly_generated_secret_key';GRANT CONNECT ON DATABASE production TO order_app;GRANT USAGE ON SCHEMA orders TO order_app;GRANT SELECT, INSERT ON orders.orders TO order_app;GRANT SELECT, INSERT ON orders.order_items TO order_app;GRANT USAGE ON SEQUENCE orders.order_id_seq TO order_app;-- Nothing else! No DELETE, no other tables, no other schemas. -- 2. Read replica user (strictly read-only)CREATE USER analytics_readonly WITH PASSWORD 'analytics_key';GRANT CONNECT ON DATABASE production TO analytics_readonly;GRANT USAGE ON SCHEMA public TO analytics_readonly;GRANT SELECT ON ALL TABLES IN SCHEMA public TO analytics_readonly;ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO analytics_readonly;-- No INSERT, UPDATE, DELETE possible -- 3. Row-Level Security (PostgreSQL example)-- Employees can only see their own department's dataALTER TABLE employee_data ENABLE ROW LEVEL SECURITY; CREATE POLICY department_isolation ON employee_data USING (department_id = current_setting('app.department_id')::integer); -- Application sets the context before queries:-- SET app.department_id = '100';-- SELECT * FROM employee_data; -- Only sees department 100 -- 4. Column-level selective access-- HR sees all columns, others don't see salary/SSNCREATE ROLE hr_full_access;GRANT SELECT ON employees TO hr_full_access; CREATE ROLE basic_employee_access;GRANT SELECT (employee_id, first_name, last_name, email, department_id) ON employees TO basic_employee_access;-- salary, ssn, birth_date, etc. NOT accessible -- 5. View-based security (alternative to column grants)CREATE VIEW employee_directory ASSELECT employee_id, first_name, last_name, email, department_idFROM employees; GRANT SELECT ON employee_directory TO directory_viewers;-- Users see the view; underlying table is inaccessible -- 6. Function-based security (PostgreSQL SECURITY DEFINER)CREATE FUNCTION get_my_salary() RETURNS DECIMAL SECURITY DEFINER -- Runs with function owner's privilegesAS $$ SELECT salary FROM employees WHERE employee_id = current_user_id();$$ LANGUAGE SQL; GRANT EXECUTE ON FUNCTION get_my_salary() TO all_employees;-- Users can see their own salary but not query the table directlyGRANT ... TO PUBLIC gives the privilege to all database users, including future users. This is almost never appropriate for production data. Use explicit user/role grants instead. If you must use PUBLIC, limit it to truly public data (reference tables, public announcements) and document the business justification.
Regulatory compliance (HIPAA, PCI-DSS, SOX, GDPR) requires not only proper access control but also the ability to demonstrate that access is properly controlled. This requires auditing both privilege configuration and privilege usage.
What Must Be Auditable:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
-- =====================================================-- Privilege Auditing Queries (PostgreSQL examples)-- ===================================================== -- 1. List all users and their rolesSELECT r.rolname AS role_name, r.rolsuper AS is_superuser, r.rolinherit AS inherits_privileges, r.rolcanlogin AS can_login, r.rolreplication AS is_replication_role, ARRAY_AGG(m.rolname) AS member_ofFROM pg_roles rLEFT JOIN pg_auth_members am ON r.oid = am.memberLEFT JOIN pg_roles m ON am.roleid = m.oidGROUP BY r.rolname, r.rolsuper, r.rolinherit, r.rolcanlogin, r.rolreplicationORDER BY r.rolname; -- 2. List all privileges on a specific tableSELECT grantee, privilege_type, is_grantableFROM information_schema.table_privilegesWHERE table_name = 'employees' AND table_schema = 'public'ORDER BY grantee, privilege_type; -- 3. List all object privileges for a specific userSELECT table_schema, table_name, privilege_typeFROM information_schema.table_privilegesWHERE grantee = 'analyst_user'ORDER BY table_schema, table_name; -- 4. Find users with superuser access (high risk)SELECT rolname, rolsuper, rolcreaterole, rolcreatedbFROM pg_rolesWHERE rolsuper = true OR rolcreaterole = true OR rolcreatedb = true; -- 5. List column-level privilegesSELECT grantee, table_schema, table_name, column_name, privilege_typeFROM information_schema.column_privilegesWHERE grantee != 'PUBLIC'ORDER BY grantee, table_schema, table_name, column_name; -- 6. Check who can access a specific schemaSELECT nspname AS schema_name, pg_catalog.pg_get_userbyid(nspowner) AS owner, nspacl AS access_privilegesFROM pg_catalog.pg_namespaceWHERE nspname = 'confidential'; -- 7. Comprehensive privilege reportWITH role_hierarchy AS ( SELECT r.rolname, ARRAY_AGG(m.rolname) FILTER (WHERE m.rolname IS NOT NULL) AS member_of_roles FROM pg_roles r LEFT JOIN pg_auth_members am ON r.oid = am.member LEFT JOIN pg_roles m ON am.roleid = m.oid GROUP BY r.rolname)SELECT rh.rolname, rh.member_of_roles, tp.table_schema, tp.table_name, ARRAY_AGG(tp.privilege_type) AS privilegesFROM role_hierarchy rhLEFT JOIN information_schema.table_privileges tp ON rh.rolname = tp.granteeWHERE tp.table_schema NOT IN ('information_schema', 'pg_catalog')GROUP BY rh.rolname, rh.member_of_roles, tp.table_schema, tp.table_nameORDER BY rh.rolname, tp.table_schema, tp.table_name;Modern organizations use automated tools to continuously monitor database privileges against security policies. Solutions like AWS Config Rules, pgAudit, SQL Server Audit, or Oracle Audit Vault can alert when privilege configurations deviate from baselines. Integrate privilege auditing into your security monitoring infrastructure.
Data Control Language is the security foundation of database systems. Through GRANT and REVOKE, DCL implements the access control policies that protect data from unauthorized access, modification, and deletion.
Let's consolidate the key concepts from this page:
What's Next:
With security mastered, we turn to the language that controls transaction behavior—Transaction Control Language (TCL). The next page explores COMMIT, ROLLBACK, and SAVEPOINT: the statements that implement the atomicity and durability guarantees essential for reliable data management.
You now understand DCL: the language that controls database access. From GRANT's permission assignment to REVOKE's access restriction, DCL provides complete security control. Role-based access, least privilege principles, and audit capabilities ensure databases remain protected. Next, we'll explore TCL—the language that manages transaction boundaries.