【AI大数据工程师特训笔记】第10讲:数据库用户、权限管理、数据库约束

目录

[1.1 用户与角色体系](#1.1 用户与角色体系)

[1.1.1 角色概念深度解析](#1.1.1 角色概念深度解析)

[1.1.2 默认系统角色](#1.1.2 默认系统角色)

[1.2 用户/角色创建与管理](#1.2 用户/角色创建与管理)

[1.2.1 创建角色的完整语法](#1.2.1 创建角色的完整语法)

[1.2.2 实际创建示例](#1.2.2 实际创建示例)

[1.2.3 角色成员关系管理](#1.2.3 角色成员关系管理)

[1.2.4 角色属性修改](#1.2.4 角色属性修改)

[1.3 权限体系详解](#1.3 权限体系详解)

[1.3.1 PostgreSQL 权限层级结构](#1.3.1 PostgreSQL 权限层级结构)

[1.3.2 数据库级别权限](#1.3.2 数据库级别权限)

[1.3.3 模式级别权限](#1.3.3 模式级别权限)

[1.3.4 表级别权限详解](#1.3.4 表级别权限详解)

[1.3.5 列级别权限](#1.3.5 列级别权限)

[1.4 权限管理实战](#1.4 权限管理实战)

[1.4.1 完整的 HR 系统权限设置](#1.4.1 完整的 HR 系统权限设置)

[1.4.2 权限验证测试](#1.4.2 权限验证测试)

[1.4.3 权限回收与审计](#1.4.3 权限回收与审计)

[1.5 高级权限特性](#1.5 高级权限特性)

[1.5.1 行级安全策略 (Row Level Security)](#1.5.1 行级安全策略 (Row Level Security))

[1.5.2 默认权限管理](#1.5.2 默认权限管理)

[1.6 数据库约束](#1.6 数据库约束)

[1.6.1 什么是约束?( What)](#1.6.1 什么是约束?( What))

[1.6.2 为什么需要约束?( Why)](#1.6.2 为什么需要约束?( Why))

[1.6.3 PostgreSQL 中常见的约束类型](#1.6.3 PostgreSQL 中常见的约束类型)

[1.6.4 实战演练:一步步添加约束](#1.6.4 实战演练:一步步添加约束)

[1.6.5 约束的命名与错误提示](#1.6.5 约束的命名与错误提示)

[1.6.6 约束的增删改查(DDL)](#1.6.6 约束的增删改查(DDL))

[1.6.7 常见错误与调试技巧](#1.6.7 常见错误与调试技巧)

[1.6.8 小结与知识图谱](#1.6.8 小结与知识图谱)


1.1 用户与角色体系

1.1.1 角色概念深度解析

概念解释:

PostgreSQL 使用统一的"角色"概念来管理数据库访问权限。与传统数据库系统不同,PostgreSQL 不严格区分"用户"和"用户组",而是将所有权限实体都视为"角色"。这种设计提供了更大的灵活性,一个角色既可以作为独立的登录账户使用,也可以作为一组权限的容器,还可以同时具备两种功能。

核心特性:

  • 统一权限模型:所有数据库访问实体都是角色,简化了权限管理。

  • 继承 机制:角色可以继承其他角色的权限,形成层次化的权限结构。

  • 灵活配置:同一个角色既可以用于登录,也可以作为权限组使用。

  • 多重身份:一个角色可以同时属于多个组,继承所有父角色的权限。

角色分类:

  • 登录角色:具备 LOGIN 权限的角色,相当于传统意义上的用户账户。

  • 组角色:主要用于权限分组,通常不直接用于登录。

  • 混合角色:既可以登录也可以包含其他角色的复合角色。

SQL 示例演示:

sql 复制代码
-- 查看系统中所有角色及其属性
SELECT rolname
      ,rolcanlogin
      ,rolsuper
      ,rolcreatedb
      ,rolcreaterole
FROM pg_roles
WHERE rolname NOT LIKE 'pg_%'
ORDER BY rolname;

-- 创建不同类型的角色示例
-- 创建登录角色(传统用户)
CREATE ROLE app_user WITH LOGIN PASSWORD 'secure_password';

-- 创建组角色(权限分组)
CREATE ROLE read_only_group NOLOGIN;

-- 创建管理角色
CREATE ROLE db_manager WITH LOGIN PASSWORD 'manager_pass' CREATEDB;

1.1.2 默认系统角色

概念解释:

PostgreSQL 预定义了一系列系统角色,这些角色具有特定的管理权限,用于执行常见的数据库管理任务。了解这些系统角色对于有效管理数据库安全至关重要。这些角色名称通常以 "pg_" 开头,具有明确定义的权限范围。

重要系统角色说明:

  • pg_read_all_data:允许读取数据库中的所有数据,无论其他权限设置如何。

  • pg_write_all_data:允许写入数据库中的所有数据,具有完整的写权限。

  • pg_monitor:允许访问监控功能和统计信息,用于数据库性能监控。

  • pg_signal_backend:允许向其他后端进程发送信号,用于管理数据库连接。

  • pg_execute_server_program:允许在数据库服务器上执行程序,权限较高需谨慎使用。

SQL 示例演示:

sql 复制代码
-- 查看预定义的系统角色及其权限
SELECT 
    rolname,
    rolcanlogin,
    rolsuper,
    rolcreatedb,
    rolcreaterole
FROM pg_roles 
WHERE rolname LIKE 'pg_%'
ORDER BY rolname;

-- 将系统角色授予普通用户
GRANT pg_read_all_data TO reporting_user;
GRANT pg_monitor TO monitoring_user;

-- 查看用户获得的系统角色权限
SELECT 
    r.rolname AS role_name,
    m.rolname AS member_name
FROM pg_roles r
JOIN pg_auth_members am ON r.oid = am.roleid
JOIN pg_roles m ON am.member = m.oid
WHERE r.rolname LIKE 'pg_%';

1.2 用户/角色创建与管理

1.2.1 创建角色的完整语法

概念解释:

在 PostgreSQL 中创建角色时,可以使用丰富的选项来精确控制角色的属性和权限。这些选项决定了角色能够执行的操作类型、资源使用限制以及安全策略。理解每个选项的含义对于创建安全的数据库账户至关重要。

主要选项详解:

  • SUPERUSER | NO SUPERUSER:决定角色是否具有超级用户权限,超级用户绕过所有权限检查

  • CREATEDB | NO CREATEDB:控制角色是否能够创建新数据库

  • CREATE ROLE | NOCREATEROLE:决定角色是否能够创建和管理其他角色

  • INHERIT | NOINHERIT:控制角色是否继承其所属组的权限

  • LOGIN | NOLOGIN:决定角色是否能够登录到数据库

  • CONNECTION LIMIT:限制角色同时建立的数据库连接数量

  • PASSWORD:设置角色的认证密码,可选择加密方式

  • VALID UNTIL:设置角色密码的有效期限,增强安全性

  • IN ROLE:指定新角色自动成为哪些角色的成员

  • ROLE:指定哪些角色自动成为新角色的成员

SQL 示例演示:

sql 复制代码
-- 创建具有不同权限级别的角色示例
-- 创建应用程序专用角色(有限权限)
CREATE ROLE web_app_user WITH
    LOGIN
    NOSUPERUSER
    NOCREATEDB
    NOCREATEROLE
    INHERIT
    CONNECTION LIMIT 1
    PASSWORD '123456'
    VALID UNTIL '2026-12-31';

-- 创建开发人员角色(中等权限)
CREATE ROLE developer WITH
    LOGIN
    NOSUPERUSER
    NOCREATEDB
    CREATEROLE
    INHERIT
    CONNECTION LIMIT 10
    PASSWORD '123456';

-- 创建数据库管理角色(较高权限)
CREATE ROLE db_administrator WITH
    LOGIN
    NOSUPERUSER
    CREATEDB
    CREATEROLE
    INHERIT
    CONNECTION LIMIT 5
    PASSWORD '123456';

1.2.2 实际创建示例

概念解释:

在实际的数据库环境中,需要根据不同的使用场景创建具有适当权限级别的角色。合理的角色设计应该遵循最小权限原则,即每个角色只拥有完成其任务所必需的最小权限集。这有助于减少安全风险和提高系统的可维护性。

角色设计原则:

  • 职责分离:根据不同工作职责创建专用角色

  • 权限最小化:只授予完成工作所必需的权限

  • 层次化设计:使用组角色来管理权限继承

  • 可维护性:角色结构应该清晰易懂,便于管理

SQL 示例演示:

sql 复制代码
-- 创建完整的角色体系示例

-- 首先创建组角色(权限容器)
CREATE ROLE read_operations NOLOGIN;
CREATE ROLE write_operations NOLOGIN;
CREATE ROLE admin_operations NOLOGIN;

-- 创建具体的工作角色并分配到相应的组
CREATE ROLE data_analyst WITH 
    LOGIN 
    PASSWORD 'analyst_pass'
    CONNECTION LIMIT 5;

CREATE ROLE application_user WITH 
    LOGIN 
    PASSWORD 'app_user_pass'
    CONNECTION LIMIT 50;

CREATE ROLE system_admin WITH 
    LOGIN 
    PASSWORD 'sys_admin_pass'
    CONNECTION LIMIT 3;

-- 建立角色成员关系
GRANT read_operations TO data_analyst;
GRANT write_operations TO application_user;
GRANT admin_operations TO system_admin;

-- 验证角色创建结果
SELECT 
    r.rolname AS role_name,
    r.rolcanlogin AS can_login,
    r.rolconnlimit AS conn_limit,
    array_agg(m.rolname) AS member_of
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
WHERE r.rolname IN ('data_analyst', 'application_user', 'system_admin')
GROUP BY r.rolname, r.rolcanlogin, r.rolconnlimit;

1.2.3 角色成员关系管理

概念解释:

角色成员关系是 PostgreSQL 权限体系的核心特性之一,它允许角色继承其他角色的权限。这种机制支持创建层次化的权限结构,大大简化了权限管理。通过将用户角色添加到组角色中,可以实现权限的批量分配和管理。

成员关系特性:

  • 权限 继承:成员角色自动获得组角色的所有权限

  • 多层次结构:组角色可以成为其他组角色的成员,形成权限继承链

  • 灵活管理:可以动态调整角色成员关系,立即生效

  • 权限累积:角色获得所有直接和间接父角色的权限

SQL 示例演示:

sql 复制代码
-- 创建复杂的角色层次结构示例

-- 创建基础权限组
CREATE ROLE base_read NOLOGIN;
CREATE ROLE base_write NOLOGIN;
CREATE ROLE base_delete NOLOGIN;

-- 创建复合权限组
CREATE ROLE standard_user NOLOGIN;
CREATE ROLE power_user NOLOGIN;
CREATE ROLE manager_level NOLOGIN;

-- 建立权限继承关系
GRANT base_read TO standard_user;
GRANT base_read, base_write TO power_user;
GRANT base_read, base_write, base_delete TO manager_level;

-- 创建具体用户角色
CREATE ROLE alice WITH LOGIN PASSWORD 'alice_pass';
CREATE ROLE bob WITH LOGIN PASSWORD 'bob_pass';
CREATE ROLE charlie WITH LOGIN PASSWORD 'charlie_pass';

-- 分配用户到权限组
GRANT standard_user TO alice;
GRANT power_user TO bob;
GRANT manager_level TO charlie;

-- 查看完整的角色继承关系
WITH RECURSIVE role_tree AS (
    SELECT 
        oid,
        rolname,
        ARRAY[]::name[] AS path
    FROM pg_roles
    WHERE rolname IN ('alice', 'bob', 'charlie')
    
    UNION ALL
    
    SELECT 
        p.oid,
        p.rolname,
        rt.path || p.rolname
    FROM pg_roles p
    JOIN pg_auth_members am ON p.oid = am.roleid
    JOIN role_tree rt ON am.member = rt.oid
)
SELECT 
    rolname AS role_name,
    array_to_string(path, ' -> ') AS inheritance_path
FROM role_tree
ORDER BY array_length(path, 1) DESC;

1.2.4 角色属性修改

概念解释:

在角色创建后,可能需要根据业务需求变化调整其属性。PostgreSQL 提供了 ALTER ROLE 命令来修改角色的各种属性,包括密码、连接限制、有效期等。合理使用角色属性修改可以增强系统安全性和适应业务变化。

可修改的属性类型:

  • 安全属性:密码、有效期、登录权限等

  • 资源限制:连接数量限制等

  • 权限标志:创建数据库、创建角色等权限

  • 基本属性:角色名称等标识信息

SQL 示例演示:

sql 复制代码
-- 角色属性修改的各种场景示例

-- 修改密码(安全增强)
ALTER ROLE alice PASSWORD 'new_secure_password_2024';

-- 设置密码有效期(临时账户)
ALTER ROLE temp_user VALID UNTIL '2024-06-30';

-- 调整连接限制(资源管理)
ALTER ROLE application_user CONNECTION LIMIT 100;

-- 禁用用户登录(账户冻结)
ALTER ROLE suspended_user NOLOGIN;

-- 启用用户登录(账户恢复)
ALTER ROLE reactivated_user LOGIN;

-- 提升用户权限(职责变更)
ALTER ROLE promoted_user CREATEROLE;

-- 重命名角色(组织变更)
ALTER ROLE old_username RENAME TO new_username;

-- 查看角色修改后的属性
SELECT 
    rolname,
    rolcanlogin,
    rolconnlimit,
    rolvaliduntil::date,
    rolcreatedb,
    rolcreaterole
FROM pg_roles 
WHERE rolname IN ('alice', 'application_user', 'promoted_user');

-- 安全操作:删除不再需要的角色
DROP ROLE IF EXISTS deprecated_user;

1.3 权限体系详解

1.3.1 PostgreSQL 权限层级结构

概念解释:

PostgreSQL 的权限管理体系采用层次化结构,从宏观到微观分为多个层级。这种层级结构确保了权限管理的精细度和灵活性。每个层级都有其特定的权限控制机制,管理员可以根据需要在不同层级上设置权限。

权限层级说明:

(1)数据库实例层级:最高层级,控制整个 PostgreSQL 实例的访问,包括创建角色、创建数据库等全局权限

(2)数据库层级:控制对特定数据库的访问权限,如连接权限、创建模式权限等

(3)模式层级:控制数据库内命名空间的访问,决定用户是否可以在模式中查看或创建对象

(4)对象层级:最细粒度层级,控制对具体数据库对象(表、视图、序列、函数等)的操作权限

权限 继承 关系:

高层级的权限会向下继承,但低层级的权限设置可以覆盖高层级的设置。这种设计既保证了权限管理的便利性,又提供了精细控制的可能性。

SQL 示例演示:

sql 复制代码
-- 查看各层级的权限示例

-- 1. 实例层级权限(角色属性)
SELECT 
    rolname,
    rolcreatedb AS can_create_db,
    rolcreaterole AS can_create_role,
    rolsuper AS is_superuser
FROM pg_roles 
WHERE rolname NOT LIKE 'pg_%';

-- 2. 数据库层级权限
SELECT 
    datname AS database_name,
    rolname AS owner_name,
    datacl AS permissions
FROM pg_database 
JOIN pg_roles ON (datdba = oid)
WHERE datname NOT LIKE 'template%';

-- 3. 模式层级权限
SELECT 
    nspname AS schema_name,
    rolname AS owner_name,
    nspacl AS permissions
FROM pg_namespace 
JOIN pg_roles ON (nspowner = oid)
WHERE nspname NOT LIKE 'pg_%';

-- 4. 表层级权限
SELECT 
    schemaname,
    tablename,
    tableowner,
    tablespace
FROM pg_tables 
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
LIMIT 10;

1.3.2 数据库级别权限

**概念解释:**数据库级别权限控制用户对整个数据库的访问能力。这些权限决定了用户是否能够连接到数据库、在数据库中创建临时表等。数据库权限是用户访问数据的第一个门槛,合理的数据库权限设置是系统安全的基础。

主要数据库权限:

  • CONNECT:允许角色连接到数据库,这是访问数据库的基本前提

  • CREATE:允许角色在数据库中创建新的模式和对象

  • TEMPORARY:允许角色在数据库中创建临时表

  • TEMPTEMPORARY 的别名,功能相同

权限管理要点:

  • 新创建的数据库默认只允许所有者连接

  • PUBLIC 角色(所有角色的隐式成员)的权限会影响所有用户

  • 合理的 CONNECT 权限控制可以限制用户访问敏感数据库

SQL 示例演示:

sql 复制代码
-- 数据库权限管理完整示例

-- 创建示例数据库
CREATE DATABASE sales_database 
    OWNER sales_owner
    ENCODING 'UTF8'
    CONNECTION LIMIT 100;

CREATE DATABASE report_database 
    OWNER report_owner
    ENCODING 'UTF8'
    CONNECTION LIMIT 20;

-- 授予数据库连接权限
GRANT CONNECT ON DATABASE sales_database TO sales_user, sales_manager;
GRANT CONNECT ON DATABASE report_database TO report_user, sales_manager;

-- 授予创建临时表权限
GRANT TEMPORARY ON DATABASE sales_database TO sales_manager;

-- 查看数据库权限分配
SELECT 
    d.datname AS database_name,
    r.rolname AS grantee,
    array_agg(privilege_type) AS privileges
FROM pg_database d
CROSS JOIN pg_roles r
CROSS JOIN (VALUES 
    ('CONNECT'), ('CREATE'), ('TEMPORARY')
) AS p(privilege_type)
WHERE has_database_privilege(r.rolname, d.oid, p.privilege_type)
AND d.datname IN ('sales_database', 'report_database')
AND r.rolname NOT LIKE 'pg_%'
GROUP BY d.datname, r.rolname
ORDER BY d.datname, r.rolname;

-- 回收数据库权限示例
REVOKE CREATE ON DATABASE sales_database FROM PUBLIC;

1.3.3 模式级别权限

**概念解释:**模式在 PostgreSQL 中作为数据库对象的命名空间和容器,模式级别权限控制用户对模式内对象的访问和操作能力。合理的模式权限设置可以实现数据逻辑隔离和访问控制。

主要模式权限:

  • USAGE:允许角色访问模式中的对象,这是使用模式内容的前提

  • CREATE:允许角色在模式中创建新对象

  • ALL:包含所有模式级别的权限

权限策略建议:

  • 为不同应用或部门创建独立的模式

  • 严格控制 CREATE 权限,避免用户随意创建对象

  • 使用默认模式搜索路径来简化权限管理

SQL 示例演示:

sql 复制代码
-- 模式权限管理详细示例

-- 创建专用模式
CREATE SCHEMA sales_data AUTHORIZATION sales_owner;
CREATE SCHEMA report_data AUTHORIZATION report_owner;
CREATE SCHEMA archive_data AUTHORIZATION archive_owner;

-- 授予模式使用权限
GRANT USAGE ON SCHEMA sales_data TO sales_user, sales_manager;
GRANT USAGE ON SCHEMA report_data TO report_user, sales_manager;

-- 授予模式创建权限(限制性)
GRANT CREATE ON SCHEMA sales_data TO sales_manager;
GRANT CREATE ON SCHEMA report_data TO report_owner;

-- 查看模式权限分配
SELECT 
    n.nspname AS schema_name,
    r.rolname AS grantee,
    array_agg(privilege_type) AS privileges
FROM pg_namespace n
CROSS JOIN pg_roles r
CROSS JOIN (VALUES 
    ('USAGE'), ('CREATE')
) AS p(privilege_type)
WHERE has_schema_privilege(r.rolname, n.oid, p.privilege_type)
AND n.nspname IN ('sales_data', 'report_data', 'archive_data')
AND r.rolname NOT LIKE 'pg_%'
GROUP BY n.nspname, r.rolname
ORDER BY n.nspname, r.rolname;

-- 设置默认权限(影响未来创建的对象)
ALTER DEFAULT PRIVILEGES IN SCHEMA sales_data 
GRANT SELECT ON TABLES TO sales_user;

ALTER DEFAULT PRIVILEGES IN SCHEMA report_data 
GRANT SELECT ON TABLES TO report_user;

1.3.4 表级别权限详解

**概念解释:**表级别权限是 PostgreSQL 权限体系中最常用和最重要的部分,它控制用户对具体数据表的操作能力。表权限提供了最细粒度的数据访问控制,可以精确管理每个用户对每张表的操作权限。

主要表权限类型:

  • SELECT:允许查询表中的数据

  • INSERT:允许向表中插入新数据

  • UPDATE:允许修改表中的现有数据

  • DELETE:允许删除表中的数据

  • TRUNCATE:允许清空整个表

  • REFERENCES:允许创建外键约束引用该表

  • TRIGGER:允许在表上创建触发器

  • ALL:包含所有表级权限

权限授予原则:

  • 遵循最小权限原则,只授予必要的权限

  • 定期审查和清理不必要的权限

  • 使用角色组来批量管理表权限

SQL 示例演示:

sql 复制代码
-- 创建示例数据表
CREATE TABLE sales_data.customers (
    customer_id SERIAL PRIMARY KEY,
    customer_name VARCHAR(100) NOT NULL,
    email VARCHAR(150) UNIQUE,
    phone VARCHAR(20),
    created_date DATE DEFAULT CURRENT_DATE,
    status VARCHAR(20) DEFAULT 'ACTIVE'
);

CREATE TABLE sales_data.orders (
    order_id SERIAL PRIMARY KEY,
    customer_id INTEGER REFERENCES sales_data.customers(customer_id),
    order_date DATE DEFAULT CURRENT_DATE,
    total_amount DECIMAL(10,2),
    order_status VARCHAR(20)
);

CREATE TABLE sales_data.products (
    product_id SERIAL PRIMARY KEY,
    product_name VARCHAR(200) NOT NULL,
    price DECIMAL(8,2),
    stock_quantity INTEGER DEFAULT 0
);

-- 授予不同级别的表权限
-- 销售用户:完整的CRUD权限
GRANT SELECT, INSERT, UPDATE, DELETE ON 
    sales_data.customers, 
    sales_data.orders 
TO sales_user;

-- 销售经理:所有权限
GRANT ALL PRIVILEGES ON 
    sales_data.customers,
    sales_data.orders,
    sales_data.products 
TO sales_manager;

-- 只读用户:仅查询权限
GRANT SELECT ON 
    sales_data.customers,
    sales_data.orders,
    sales_data.products 
TO report_user;

-- 查看表权限分配详情
SELECT 
    schemaname,
    tablename,
    grantee,
    privilege_type
FROM information_schema.table_privileges 
WHERE schemaname = 'sales_data'
AND grantee IN ('sales_user', 'sales_manager', 'report_user')
ORDER BY tablename, grantee, privilege_type;

1.3.5 列级别权限

**概念解释:**列级别权限提供了比表权限更细粒度的访问控制,允许管理员精确控制用户对表中特定列的访问权限。这种权限控制特别适用于包含敏感信息的表,如身份证号、薪资等隐私数据。

适用场景:

  • 保护敏感个人信息

  • 限制对业务关键数据的修改

  • 实现数据脱敏访问

  • 满足合规性要求

权限限制:

  • 列级权限只能控制 SELECT 和 UPDATE 操作

  • INSERT 权限需要在表级别授予,但可以限制插入时某些列的值

  • 列级权限与表级权限结合使用

SQL 示例演示:

sql 复制代码
-- 创建包含敏感信息的员工表
CREATE TABLE hr_data.employees (
    employee_id SERIAL PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    phone VARCHAR(20),
    salary DECIMAL(10,2),
    bonus DECIMAL(8,2),
    social_security VARCHAR(20),
    home_address TEXT,
    department_id INTEGER,
    hire_date DATE DEFAULT CURRENT_DATE
);

-- 授予列级权限
-- 普通HR员工:可以访问基本信息,但不能查看薪资
GRANT SELECT (
    employee_id, first_name, last_name, email, phone, department_id, hire_date
) ON hr_data.employees TO hr_assistant;

-- 经理:可以查看薪资,但不能查看社保和住址
GRANT SELECT (
    employee_id, first_name, last_name, email, phone, salary, bonus, department_id, hire_date
) ON hr_data.employees TO hr_manager;

GRANT UPDATE (
    first_name, last_name, email, phone, department_id
) ON hr_data.employees TO hr_manager;

-- 薪资专员:可以查看和更新薪资信息
GRANT SELECT (employee_id, salary, bonus) ON hr_data.employees TO payroll_clerk;
GRANT UPDATE (salary, bonus) ON hr_data.employees TO payroll_clerk;

-- 验证列级权限设置
SELECT 
    table_schema,
    table_name,
    column_name,
    grantee,
    privilege_type
FROM information_schema.column_privileges 
WHERE table_schema = 'hr_data' 
AND table_name = 'employees'
ORDER BY grantee, column_name, privilege_type;

-- 测试权限效果(以hr_assistant身份)
-- 以下查询应该成功
SELECT employee_id, first_name, last_name, email FROM hr_data.employees;

-- 以下查询应该失败(权限不足)
SELECT salary, social_security FROM hr_data.employees;

1.4 权限管理实战

1.4.1 完整的 HR 系统权限设置

**概念解释:**在实际的企业环境中,需要设计完整的权限体系来满足不同部门和角色的需求。以 HR 系统为例,我们需要为不同层级的员工设计适当的权限方案,确保数据安全和工作效率的平衡。

设计原则:

  • 角色分离:不同职责的用户拥有不同的权限

  • 数据分类:根据数据敏感程度设置不同的访问级别

  • 审计跟踪:记录关键操作以便追踪

  • 定期审查:定期检查和调整权限设置

权限层级设计:

  1. HR 助理:基础数据录入和查询

  2. HR 专员:扩展的数据管理权限

  3. HR 经理:敏感数据访问和审批权限

  4. 薪资专员:专门的薪资数据处理权限

  5. 系统 管理员:完整的系统管理权限

SQL 示例演示:

sql 复制代码
-- 创建完整的HR系统权限体系

-- 第一步:创建专用角色
CREATE ROLE hr_system NOLOGIN;
CREATE ROLE hr_assistant WITH LOGIN PASSWORD 'assistant123';
CREATE ROLE hr_specialist WITH LOGIN PASSWORD 'specialist123';
CREATE ROLE hr_manager WITH LOGIN PASSWORD 'manager123';
CREATE ROLE payroll_clerk WITH LOGIN PASSWORD 'payroll123';
CREATE ROLE hr_director WITH LOGIN PASSWORD 'director123';

-- 第二步:建立角色层次
GRANT hr_system TO hr_assistant, hr_specialist, hr_manager, payroll_clerk, hr_director;

-- 第三步:创建HR专用模式和数据表
CREATE SCHEMA hr_management AUTHORIZATION hr_director;

CREATE TABLE hr_management.employees (
    emp_id SERIAL PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    phone VARCHAR(20),
    hire_date DATE NOT NULL,
    department VARCHAR(50),
    position VARCHAR(50),
    base_salary DECIMAL(10,2),
    performance_bonus DECIMAL(8,2),
    social_id VARCHAR(20),
    bank_account VARCHAR(50),
    emergency_contact TEXT,
    created_by VARCHAR(50) DEFAULT CURRENT_USER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE hr_management.salary_history (
    history_id SERIAL PRIMARY KEY,
    emp_id INTEGER REFERENCES hr_management.employees(emp_id),
    old_salary DECIMAL(10,2),
    new_salary DECIMAL(10,2),
    change_reason TEXT,
    effective_date DATE,
    approved_by VARCHAR(50),
    change_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE hr_management.performance_reviews (
    review_id SERIAL PRIMARY KEY,
    emp_id INTEGER REFERENCES hr_management.employees(emp_id),
    review_date DATE,
    reviewer VARCHAR(50),
    rating INTEGER,
    comments TEXT,
    goals TEXT,
    confidential_notes TEXT
);

-- 第四步:精细化的权限分配

-- HR助理:基础数据维护权限
GRANT USAGE ON SCHEMA hr_management TO hr_assistant;
GRANT SELECT, INSERT, UPDATE ON hr_management.employees TO hr_assistant;
GRANT SELECT (emp_id, first_name, last_name, department, position) 
ON hr_management.employees TO hr_assistant;

-- HR专员:扩展权限
GRANT USAGE ON SCHEMA hr_management TO hr_specialist;
GRANT SELECT, INSERT, UPDATE ON 
    hr_management.employees,
    hr_management.performance_reviews
TO hr_specialist;
GRANT SELECT (review_id, emp_id, review_date, rating, comments, goals) 
ON hr_management.performance_reviews TO hr_specialist;

-- HR经理:敏感数据访问
GRANT USAGE ON SCHEMA hr_management TO hr_manager;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA hr_management TO hr_manager;
GRANT ALL PRIVILEGES ON hr_management.performance_reviews TO hr_manager;

-- 薪资专员:专门的薪资权限
GRANT USAGE ON SCHEMA hr_management TO payroll_clerk;
GRANT SELECT (emp_id, first_name, last_name, base_salary, bank_account) 
ON hr_management.employees TO payroll_clerk;
GRANT SELECT, INSERT ON hr_management.salary_history TO payroll_clerk;

-- HR总监:完整权限
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA hr_management TO hr_director;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA hr_management TO hr_director;

-- 第五步:设置序列权限
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA hr_management TO hr_system;

-- 查看完整的权限分配
SELECT 
    table_schema,
    table_name,
    grantee,
    string_agg(privilege_type, ', ' ORDER BY privilege_type) AS privileges
FROM information_schema.table_privileges 
WHERE table_schema = 'hr_management'
GROUP BY table_schema, table_name, grantee
ORDER BY table_name, grantee;

1.4.2 权限验证测试

**概念解释:**权限设置完成后,必须进行全面的测试验证,确保权限配置符合设计预期。测试应该覆盖正常操作和异常情况,验证权限的正确性和安全性。

测试策略:

  • 正向测试:验证用户能够执行其权限范围内的操作

  • 负向测试:验证用户不能执行其权限范围外的操作

  • 边界测试:验证权限边界的正确处理

  • 集成测试:验证多个权限组合的效果

SQL 示例演示:

sql 复制代码
-- 权限验证测试脚本

-- 测试1:HR助理权限验证
SET ROLE hr_assistant;

-- 应该成功:查询员工基本信息
SELECT emp_id, first_name, last_name, department 
FROM hr_management.employees 
LIMIT 5;

-- 应该失败:查询薪资信息
SELECT base_salary, performance_bonus 
FROM hr_management.employees 
LIMIT 5;
-- 预期:权限拒绝错误

-- 应该成功:更新基础信息
UPDATE hr_management.employees 
SET phone = '123-456-7890' 
WHERE emp_id = 1;

-- 应该失败:更新薪资信息
UPDATE hr_management.employees 
SET base_salary = 50000 
WHERE emp_id = 1;
-- 预期:权限拒绝错误

-- 测试2:HR专员权限验证
SET ROLE hr_specialist;

-- 应该成功:查询绩效评估信息
SELECT review_id, emp_id, rating, comments 
FROM hr_management.performance_reviews 
LIMIT 5;

-- 应该失败:查询保密笔记
SELECT confidential_notes 
FROM hr_management.performance_reviews 
LIMIT 5;
-- 预期:权限拒绝错误

-- 测试3:薪资专员权限验证
SET ROLE payroll_clerk;

-- 应该成功:查询薪资和银行账户信息
SELECT emp_id, first_name, last_name, base_salary, bank_account 
FROM hr_management.employees 
LIMIT 5;

-- 应该失败:查询社保信息
SELECT social_id 
FROM hr_management.employees 
LIMIT 5;
-- 预期:权限拒绝错误

-- 应该成功:记录薪资历史
INSERT INTO hr_management.salary_history 
(emp_id, old_salary, new_salary, change_reason, effective_date)
VALUES (1, 45000, 48000, '年度调薪', '2024-01-01');

-- 测试4:综合权限验证报告
RESET ROLE;

-- 生成权限验证报告
SELECT 
    r.rolname AS test_user,
    t.table_name,
    array_agg(p.privilege_type ORDER BY p.privilege_type) AS actual_privileges,
    CASE 
        WHEN r.rolname = 'hr_assistant' AND t.table_name = 'employees' THEN 
            ARRAY['INSERT', 'SELECT', 'UPDATE']::text[]
        WHEN r.rolname = 'payroll_clerk' AND t.table_name = 'employees' THEN 
            ARRAY['SELECT']::text[]
        -- 可以添加更多预期的权限配置
        ELSE ARRAY[]::text[]
    END AS expected_privileges,
    CASE 
        WHEN array_agg(p.privilege_type ORDER BY p.privilege_type) = 
             CASE 
                 WHEN r.rolname = 'hr_assistant' AND t.table_name = 'employees' THEN 
                     ARRAY['INSERT', 'SELECT', 'UPDATE']::text[]
                 WHEN r.rolname = 'payroll_clerk' AND t.table_name = 'employees' THEN 
                     ARRAY['SELECT']::text[]
                 ELSE ARRAY[]::text[]
             END THEN 'PASS'
        ELSE 'FAIL'
    END AS test_result
FROM information_schema.table_privileges p
JOIN pg_roles r ON p.grantee = r.rolname
JOIN information_schema.tables t ON p.table_schema = t.table_schema 
                                AND p.table_name = t.table_name
WHERE p.table_schema = 'hr_management'
AND r.rolname IN ('hr_assistant', 'hr_specialist', 'hr_manager', 'payroll_clerk')
GROUP BY r.rolname, t.table_name
ORDER BY test_result, test_user, table_name;

1.4.3 权限回收与审计

**概念解释:**权限管理是一个持续的过程,需要定期审查和调整。权限回收是安全治理的重要环节,而权限审计则帮助管理员了解当前的权限状态并发现潜在的安全风险。

权限回收场景:

  • 员工离职或转岗

  • 项目结束或变更

  • 安全策略调整

  • 发现权限滥用

审计重点:

  • 超级用户权限分配

  • 敏感数据访问权限

  • 权限变更历史

  • 异常权限组合

SQL 示例演示:

sql 复制代码
-- 权限回收与审计管理

-- 场景1:员工转岗权限调整
-- 从HR专员晋升为HR经理,需要增加权限
GRANT hr_manager TO hr_specialist;

-- 场景2:员工离职权限回收
-- 回收所有直接权限
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA hr_management FROM former_employee;
REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA hr_management FROM former_employee;
REVOKE USAGE ON SCHEMA hr_management FROM former_employee;

-- 从所有组角色中移除
REVOKE hr_system FROM former_employee;

-- 禁用登录
ALTER ROLE former_employee NOLOGIN;

-- 权限审计查询

-- 审计1:查看所有用户的权限摘要
SELECT 
    r.rolname AS username,
    r.rolcanlogin AS can_login,
    COUNT(DISTINCT tp.table_name) AS tables_with_access,
    COUNT(DISTINCT cp.column_name) AS columns_with_access,
    COUNT(DISTINCT fp.routine_name) AS functions_with_access,
    array_agg(DISTINCT rm.rolname) AS member_of_groups
FROM pg_roles r
LEFT JOIN information_schema.table_privileges tp ON r.rolname = tp.grantee
LEFT JOIN information_schema.column_privileges cp ON r.rolname = cp.grantee
LEFT JOIN information_schema.routine_privileges fp ON r.rolname = fp.grantee
LEFT JOIN pg_auth_members am ON r.oid = am.member
LEFT JOIN pg_roles rm ON am.roleid = rm.oid
WHERE r.rolname NOT LIKE 'pg_%'
AND r.rolname NOT IN ('postgres')
GROUP BY r.rolname, r.rolcanlogin
ORDER BY tables_with_access DESC, username;

-- 审计2:敏感表权限检查
SELECT 
    table_schema,
    table_name,
    grantee,
    string_agg(privilege_type, ', ' ORDER BY privilege_type) AS privileges
FROM information_schema.table_privileges 
WHERE table_name IN ('employees', 'salary_history', 'performance_reviews')
AND table_schema = 'hr_management'
AND privilege_type IN ('INSERT', 'UPDATE', 'DELETE', 'SELECT')
GROUP BY table_schema, table_name, grantee
ORDER BY table_name, grantee;

-- 审计3:权限变更跟踪(需要提前启用审计)
-- 创建权限审计表
CREATE TABLE IF NOT EXISTS security.privilege_audit (
    audit_id SERIAL PRIMARY KEY,
    change_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    changed_by VARCHAR(50) DEFAULT CURRENT_USER,
    object_type VARCHAR(20),
    object_name VARCHAR(100),
    grantee VARCHAR(50),
    privilege_type VARCHAR(20),
    action_type VARCHAR(10), -- GRANT/REVOKE
    sql_command TEXT
);

-- 审计4:异常权限检测
-- 检测具有过多权限的用户
SELECT 
    grantee,
    COUNT(*) AS privilege_count,
    COUNT(DISTINCT table_name) AS table_count,
    COUNT(DISTINCT privilege_type) AS privilege_types
FROM information_schema.table_privileges 
WHERE table_schema NOT IN ('information_schema', 'pg_catalog')
GROUP BY grantee
HAVING COUNT(*) > 20  -- 阈值可根据实际情况调整
ORDER BY privilege_count DESC;

-- 权限清理脚本示例
-- 查找并回收长期未使用的权限
SELECT 
    'REVOKE ' || privilege_type || ' ON ' || table_schema || '.' || table_name || 
    ' FROM ' || grantee || ';' AS revoke_command
FROM information_schema.table_privileges 
WHERE grantee IN (
    SELECT rolname 
    FROM pg_roles 
    WHERE rolname NOT IN (
        SELECT DISTINCT usename 
        FROM pg_stat_activity 
        WHERE state = 'active'
    )
    AND rolname NOT LIKE 'pg_%'
)
AND table_schema = 'hr_management';

1.5 高级权限特性

1.5.1 行级安全策略 (Row Level Security)

概念解释:

行级安全策略(RLS)是 PostgreSQL 的高级安全特性,它允许在表级别设置基于行数据的访问控制规则。与传统的权限控制不同,RLS 可以根据数据内容动态决定用户能够访问哪些行,实现真正意义上的数据级安全。

核心概念:

  • 策略(Policy):定义数据访问规则的数据库对象

  • USING **子句:**指定哪些行对用户可见(SELECT 权限)

  • WITH CHECK **子句:**指定哪些行可以被修改(INSERT/UPDATE 权限)

  • 强制策略:RLS 启用后,所有数据访问都必须通过策略检查

适用场景:

  • 多租户数据隔离

  • 部门数据权限控制

  • 个人数据隐私保护

  • 合规性数据访问控制

SQL 示例演示:

sql 复制代码
-- 行级安全策略实战示例

-- 创建多租户数据表
CREATE TABLE business.sales_records (
    record_id SERIAL PRIMARY KEY,
    tenant_id INTEGER NOT NULL,
    salesperson VARCHAR(50) NOT NULL,
    sale_amount DECIMAL(10,2) NOT NULL,
    sale_date DATE DEFAULT CURRENT_DATE,
    customer_name VARCHAR(100),
    product_category VARCHAR(50),
    confidential_notes TEXT
);

-- 插入测试数据
INSERT INTO business.sales_records 
(tenant_id, salesperson, sale_amount, customer_name, product_category, confidential_notes)
VALUES 
(1, 'alice', 1500.00, '客户A', '电子产品', '重要客户,需要重点维护'),
(1, 'bob', 2300.00, '客户B', '办公用品', '价格敏感型客户'),
(2, 'charlie', 1800.00, '客户C', '家具', '新开发客户,潜力较大'),
(2, 'david', 3200.00, '客户D', '电器', '长期合作客户');

-- 创建租户用户
CREATE ROLE tenant1_user WITH LOGIN PASSWORD 'tenant1_pass';
CREATE ROLE tenant2_user WITH LOGIN PASSWORD 'tenant2_pass';
CREATE ROLE sales_director WITH LOGIN PASSWORD 'director_pass';

-- 授予基础权限
GRANT USAGE ON SCHEMA business TO tenant1_user, tenant2_user, sales_director;
GRANT SELECT, INSERT, UPDATE ON business.sales_records TO tenant1_user, tenant2_user, sales_director;

-- 启用行级安全
ALTER TABLE business.sales_records ENABLE ROW LEVEL SECURITY;

-- 创建租户隔离策略
-- 租户1用户只能访问租户1的数据
CREATE POLICY tenant1_policy ON business.sales_records
    FOR ALL
    TO tenant1_user
    USING (tenant_id = 1)
    WITH CHECK (tenant_id = 1);

-- 租户2用户只能访问租户2的数据
CREATE POLICY tenant2_policy ON business.sales_records
    FOR ALL
    TO tenant2_user
    USING (tenant_id = 2)
    WITH CHECK (tenant_id = 2);

-- 销售总监可以访问所有数据
CREATE POLICY director_policy ON business.sales_records
    FOR ALL
    TO sales_director
    USING (true)
    WITH CHECK (true);

-- 创建基于用户角色的数据访问策略
-- 假设我们有一个用户-租户映射表
CREATE TABLE business.user_tenant_mapping (
    username VARCHAR(50) PRIMARY KEY,
    tenant_id INTEGER NOT NULL,
    user_role VARCHAR(20) DEFAULT 'user'
);

INSERT INTO business.user_tenant_mapping VALUES
('tenant1_user', 1, 'user'),
('tenant2_user', 2, 'user'),
('sales_director', 0, 'director');

-- 创建更复杂的策略:用户只能查看自己创建的销售记录
CREATE POLICY user_own_records_policy ON business.sales_records
    FOR SELECT
    TO PUBLIC
    USING (salesperson = CURRENT_USER);

-- 经理可以查看本部门所有记录
CREATE POLICY manager_department_policy ON business.sales_records
    FOR SELECT
    TO sales_director
    USING (EXISTS (
        SELECT 1 FROM business.user_tenant_mapping 
        WHERE username = CURRENT_USER 
        AND user_role IN ('manager', 'director')
    ));

-- 测试行级安全策略
-- 以租户1用户身份测试
SET ROLE tenant1_user;
SELECT * FROM business.sales_records;
-- 应该只返回租户1的数据

-- 尝试插入其他租户的数据(应该失败)
INSERT INTO business.sales_records 
(tenant_id, salesperson, sale_amount, customer_name)
VALUES (2, 'tenant1_user', 1000.00, '测试客户');
-- 预期:违反策略错误

-- 以销售总监身份测试
RESET ROLE;
SET ROLE sales_director;
SELECT * FROM business.sales_records;
-- 应该返回所有数据

-- 查看行级安全策略状态
SELECT 
    schemaname,
    tablename,
    policyname,
    permissive,
    roles,
    cmd,
    qual,
    with_check
FROM pg_policies 
WHERE schemaname = 'business'
ORDER BY tablename, policyname;

1.5.2 默认权限管理

概念解释: 默认权限(Default Privileges)允许管理员为未来创建的数据库对象预设权限规则。这种机制确保了新创建的对象自动获得适当的权限设置,大大简化了权限管理工作。

默认权限特性:

  • 作用范围:可以针对特定角色、特定模式设置默认权限

  • 对象类型:支持表、视图、序列、函数、类型等对象

  • 继承性:默认权限会影响所有后续创建的对象

  • 可覆盖性:创建对象后可以修改默认权限设置

管理要点:

  • 合理设置默认权限减少手动权限分配工作

  • 定期审查默认权限设置

  • 注意默认权限与显式权限的优先级

SQL 示例演示:

sql 复制代码
-- 默认权限管理详细示例

-- 查看当前的默认权限设置
SELECT 
    defaclrole::regrole AS role_name,
    defaclnamespace::regnamespace AS schema_name,
    defaclobjtype AS object_type,
    defaclacl AS default_privileges
FROM pg_default_acl;

-- 为不同场景设置默认权限

-- 场景1:为HR模式设置默认权限
-- 所有新表对HR组角色可读
ALTER DEFAULT PRIVILEGES IN SCHEMA hr_management
GRANT SELECT ON TABLES TO hr_system;

-- 新序列对HR组角色可用
ALTER DEFAULT PRIVILEGES IN SCHEMA hr_management  
GRANT USAGE, SELECT ON SEQUENCES TO hr_system;

-- 新函数对HR组角色可执行
ALTER DEFAULT PRIVILEGES IN SCHEMA hr_management
GRANT EXECUTE ON FUNCTIONS TO hr_system;

-- 场景2:为特定角色创建的对象设置默认权限
-- 当hr_director创建对象时,自动授予HR经理权限
ALTER DEFAULT PRIVILEGES FOR ROLE hr_director
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO hr_manager;

ALTER DEFAULT PRIVILEGES FOR ROLE hr_director
GRANT EXECUTE ON FUNCTIONS TO hr_manager;

-- 场景3:为应用程序模式设置严格的默认权限
ALTER DEFAULT PRIVILEGES IN SCHEMA app_data
REVOKE ALL ON TABLES FROM PUBLIC;

ALTER DEFAULT PRIVILEGES IN SCHEMA app_data
GRANT SELECT ON TABLES TO app_readonly_role;

ALTER DEFAULT PRIVILEGES IN SCHEMA app_data
GRANT SELECT, INSERT, UPDATE ON TABLES TO app_user_role;

-- 场景4:设置跨模式的默认权限
-- 为未来创建的所有模式设置基础权限
ALTER DEFAULT PRIVILEGES 
GRANT USAGE ON SCHEMAS TO PUBLIC;

ALTER DEFAULT PRIVILEGES 
REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;

-- 测试默认权限效果

-- 创建新表测试默认权限
SET ROLE hr_director;
CREATE TABLE hr_management.new_employee_skills (
    skill_id SERIAL PRIMARY KEY,
    emp_id INTEGER,
    skill_name VARCHAR(100),
    proficiency_level VARCHAR(20),
    certified BOOLEAN DEFAULT false
);

-- 自动创建序列
CREATE SEQUENCE hr_management.skill_certification_seq;

-- 验证默认权限是否正确应用
SELECT 
    table_schema,
    table_name,
    grantee,
    privilege_type
FROM information_schema.table_privileges 
WHERE table_name = 'new_employee_skills'
ORDER BY grantee, privilege_type;

SELECT 
    sequence_schema,
    sequence_name, 
    grantee,
    privilege_type
FROM information_schema.sequence_privileges 
WHERE sequence_name = 'skill_certification_seq'
ORDER BY grantee, privilege_type;

-- 默认权限维护脚本

-- 生成当前默认权限的报告
SELECT 
    'ALTER DEFAULT PRIVILEGES ' ||
    CASE 
        WHEN defaclrole != 0 THEN 'FOR ROLE ' || defaclrole::regrole
        ELSE ''
    END ||
    CASE 
        WHEN defaclnamespace != 0 THEN ' IN SCHEMA ' || defaclnamespace::regnamespace
        ELSE ''
    END ||
    ' GRANT ' || privilege_type || ' ON ' || object_type || ' TO ' || grantee || ';' AS default_privilege_ddl
FROM (
    SELECT 
        defaclrole,
        defaclnamespace,
        defaclobjtype,
        (aclexplode(defaclacl)).grantee AS grantee_oid,
        (aclexplode(defaclacl)).privilege_type AS privilege_type,
        CASE defaclobjtype
            WHEN 'r' THEN 'TABLES'
            WHEN 'S' THEN 'SEQUENCES'
            WHEN 'f' THEN 'FUNCTIONS'
            WHEN 'T' THEN 'TYPES'
        END AS object_type
    FROM pg_default_acl
) d
JOIN pg_roles r ON d.grantee_oid = r.oid
WHERE r.rolname NOT LIKE 'pg_%'
ORDER BY defaclrole, defaclnamespace, object_type, r.rolname;

-- 清理不必要的默认权限
-- 例如:回收PUBLIC角色的默认权限
ALTER DEFAULT PRIVILEGES 
REVOKE ALL ON TABLES FROM PUBLIC;

ALTER DEFAULT PRIVILEGES 
REVOKE ALL ON SEQUENCES FROM PUBLIC;

ALTER DEFAULT PRIVILEGES 
REVOKE ALL ON FUNCTIONS FROM PUBLIC;

ALTER DEFAULT PRIVILEGES 
REVOKE ALL ON TYPES FROM PUBLIC;

1.6 数据库约束

在前面的内容中,我们已经学习了如何创建表、插入数据、查询数据。但数据不仅仅是"存进去",更重要的是"存得对"。

约束(Constraints) 就是数据库用来保证数据"合法、正确、完整"的一种机制。

本节将从零开始,带你理解什么是约束、为什么需要它,以及 PostgreSQL 中常见的几种约束如何使用。

1.6.1 什么是约束?( What)

约束(Constraint) 是对表中数据的一种"规则"或"限制",用于保证数据的完整性和一致性。

举个例子:

假设你有一个 users 表,里面有一个 email 字段。你希望:

  • 每个用户的邮箱是唯一的(不能重复)

  • 邮箱不能为空

  • 年龄必须大于 0

这些"规则"就可以用 约束 来实现。

1.6.2 为什么需要约束?( Why)

没有约束的问题 有约束的解决方案
插入了重复的邮箱 使用 UNIQUE 约束防止重复
年龄插入了 -5 使用 CHECK 约束限制范围
订单没有关联用户 使用 FOREIGN KEY 约束保证引用完整性

**总结:**约束是数据库的"守门员",防止"脏数据"进入系统。

1.6.3 PostgreSQL 中常见的约束类型

约束类型 作用 关键字
NOT NULL 禁止列为空 NOT NULL
UNIQUE 保证列值唯一 UNIQUE
PRIMARY KEY 主键,非空且唯一 PRIMARY KEY
CHECK 自定义条件检查 CHECK
FOREIGN KEY 外键,保证引用完整性 REFERENCES
DEFAULT 默认值(不是严格约束,但常一起用) DEFAULT

1.6.4 实战演练:一步步添加约束

我们用一个电商系统的例子来讲解:usersordersproducts 三个表。

(1)创建用户表(users)

sql 复制代码
CREATE TABLE users (
    id SERIAL PRIMARY KEY,          -- 主键,自动增长
    email VARCHAR(255) UNIQUE NOT NULL,  -- 唯一且非空
    age INT CHECK (age > 0),        -- 年龄必须大于0
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

(2)创建产品表(products)

sql 复制代码
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    price NUMERIC(10,2) CHECK (price >= 0)  -- 价格不能为负
);

(3)创建订单表(orders)

sql 复制代码
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INT REFERENCES users(id) ON DELETE CASCADE,  -- 外键
    product_id INT REFERENCES products(id),
    quantity INT CHECK (quantity > 0),
    order_date DATE DEFAULT CURRENT_DATE
);

1.6.5 约束的命名与错误提示

(1)给约束起名字(推荐)

sql 复制代码
CREATE TABLE users (
    id SERIAL,
    email VARCHAR(255),
    CONSTRAINT users_pk PRIMARY KEY (id),
    CONSTRAINT users_email_unique UNIQUE (email),
    CONSTRAINT users_email_not_null CHECK (email IS NOT NULL)
);

**好处:**报错信息更清晰,便于调试和维护。

(2)不命名约束(不推荐)

sql 复制代码
CREATE TABLE users (
    email VARCHAR(255) UNIQUE
);

报错信息可能是:

sql 复制代码
ERROR:  duplicate key value violates unique constraint "users_email_key"

1.6.6 约束的增删改查(DDL)

(1)添加约束(表已存在)

sql 复制代码
-- 添加唯一约束
ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);

-- 添加检查约束
ALTER TABLE users ADD CONSTRAINT users_age_check CHECK (age > 0);

(2)删除约束

sql 复制代码
ALTER TABLE users DROP CONSTRAINT users_email_unique;

1.6.7 常见错误与调试技巧

错误提示 原因 解决方法
violates not-null constraint 插入了 NULL 值 检查插入值或修改表结构
violates unique constraint 插入了重复值 检查数据是否已存在
insert or update on table violates foreign key constraint 外键引用的值不存在 先插入主表数据
check constraint violated 不满足 CHECK 条件 检查插入值是否符合条件

1.6.8 小结与知识图谱

sql 复制代码
约束(Constraints)
├── 单列约束
│   ├── NOT NULL
│   ├── UNIQUE
│   ├── CHECK
│   └── DEFAULT
├── 多列约束
│   └── PRIMARY KEY (id, email)
└── 表间约束
    └── FOREIGN KEY
相关推荐
标书畅畅行1 小时前
2026 年 AI 标书工具市场观察:技术迭代与选型指南
大数据·人工智能
凤山老林2 小时前
DDD(领域驱动设计)在复杂业务系统中的落地指南
java·开发语言·数据库·ddd·领域驱动
黎阳之光2 小时前
视频孪生+空天地水工融合,黎阳之光构建智慧水利监测新范式
大数据·人工智能·物联网·算法·安全
凯瑟琳.奥古斯特2 小时前
子查询原理与实战案例解析
开发语言·数据库·职场和发展·数据库开发
KaMeidebaby2 小时前
卡梅德生物技术快报|酵母双杂交 cDNA 文库构建与蛋白互作筛选流程
服务器·前端·数据库·人工智能·算法
暴躁小师兄数据学院2 小时前
【AI大数据工程师特训笔记】第02讲:PostgreSQL数据库生态全景
大数据·数据库·人工智能·postgresql
沐风___2 小时前
App 上架之后:如何看数据、获取用户与持续迭代产品
服务器·前端·数据库
兔子宇航员03012 小时前
HIVE SQL 中 NULL 值在 JOIN 和 GROUP BY 中的致命陷阱与解决方案
hive·hadoop·sql
暴躁小师兄数据学院2 小时前
【AI大模型应用开发工程师特训笔记】第04讲(第9章):文件目录操作
人工智能·笔记·python