访问控制列表(ACL,Access Control List)是大云海山数据库实现权限管理的核心组件,它定义了数据库对象(如表、视图、函数等)与角色(用户或组)之间的操作权限。其核心职能是统一承载数据库系统中各类对象的权限表示、授权与回收、权限检查全生命周期管理,实现细粒度的安全隔离。
一、ACL 的基本概念与结构
图1.1 ACL基本结构
每个数据库对象(如表、视图、函数等)都拥有其独立的访问控制列表(ACL),用于记录不同角色对该对象的操作权限。ACL 在系统表中以 aclitem数组的形式存储,基本结构如图 1.1 所示。AclItem 结构体包含 ai_grantee(被授予者)、ai_grantor(授予者)和 ai_privs(32 位权限位,低 16 位为实际权限位,高 16 位为授权选项位),每个元素代表一条权限授予记录。多个 AclItem 以数组形式顺序排列,在权限检查时,系统从对象的 relacl 等字段加载 ACL 数组,按角色匹配规则逐项扫描,并结合角色成员关系递归判断;授权或回收操作则通过 aclupdate 等函数对数组进行增、删、改,最终将新数组写回系统表,并触发缓存失效,确保后续权限判断的准确性。
权限类型采用位掩码(AclMode)设计,如表格1.2所示,每种权限对应一个独立的比特位,同时每个权限位都有一个字符表示,用于 aclitem 类型的输入/输出。权限位共 32 位,低 16 位为实际权限,高 16 位为对应的授权选项(GRANT OPTION)。
权限宏 | 位值 | 标志位说明(代表什么权限) |
|---|---|---|
ACL_INSERT | 1<<0 | 允许向表中插入新行(INSERT) |
ACL_SELECT | 1<<1 | 允许查询表、视图或序列(SELECT) |
ACL_UPDATE | 1<<2 | 允许更新表中现有行(UPDATE) |
ACL_DELETE | 1<<3 | 允许删除表中行(DELETE) |
ACL_TRUNCATE | 1<<4 | 允许截断表(TRUNCATE) |
ACL_REFERENCES | 1<<5 | 允许创建引用该表的外键约束(REFERENCES) |
ACL_TRIGGER | 1<<6 | 允许在表上创建触发器(TRIGGER) |
ACL_EXECUTE | 1<<7 | 允许执行函数或存储过程(EXECUTE) |
ACL_USAGE | 1<<8 | 允许使用模式、序列、类型、语言等对象(USAGE) |
ACL_CREATE | 1<<9 | 允许在数据库或模式中创建新对象(CREATE) |
ACL_CREATE_TEMP | 1<<10 | 允许在数据库中创建临时表(TEMP) |
ACL_CONNECT | 1<<11 | 允许连接到数据库(CONNECT) |
ACL_SET | 1<<12 | 允许设置指定配置参数(SET) |
ACL_ALTER_SYSTEM | 1<<13 | 允许修改系统级配置参数(ALTER SYSTEM) |
表1.2 AclMode权限类型
二、ACL 的初始化
ACL的初始化工作在数据库集群的 initdb 阶段完成。其核心操作由 initdb 中的 bootstrap 模式执行,主要包含以下三个步骤:
(1)创建超级用户
系统在 pg_authid 系统表中插入超级用户记录(默认与操作系统用户同名)。该角色拥有所有数据库对象的全部权限,且其权限检查路径被优化为直接放行,无需遍历 ACL 数组。
(2)初始化系统表 ACL
所有系统表的ACL字段初始化为NULL。NULL的ACL语义表示“使用默认权限”——即对象所有者拥有该对象类型定义的全部权限,其他角色(包括 PUBLIC)无任何权限。
(3)设置公共模式默认权限
对于 public 模式(OID 固定为 11),系统通过defacl机制预先设置默认权限,允许 PUBLIC 角色(ACL_ID_PUBLIC)拥有USAGE权限。这使得任何用户都能连接到数据库并查看 public 模式下的对象,但需要对象自身权限才能进一步操作。
三、内存中的 ACL 缓存
为了提升权限检查效率,对象的 ACL 缓存在内存中,避免重复从磁盘系统表中解析。ACL 缓存与对象描述符关联,其生命周期由缓存失效机制统一管理。
(1)缓存结构
每个对象的 ACL 以 ArrayType形式存储在内存中,并通过对象描述符的专用字段引用。以表对象为例,RelationData 结构体中的 rd_acl 指针指向该表的 ACL 缓存。
(2)缓存构建时机
rd_acl 初始为NULL,在首次执行权限检查时,系统通过 SysCache从 pg_class.relacl 字段获取原始数据,调用 DatumGetAclP 反序列化为 Acl*,并赋值给 rd_acl。后续权限检查直接复用该指针,无需重复加载。
(3)缓存所在内存上下文
ACL 缓存使用 palloc 分配在 CacheMemoryContext 中,该上下文专用于缓存系统表数据,与 Relation 描述符所在上下文一致。当 Relation 关闭时,rd_acl 随同描述符一起被释放。
(4)缓存失效机制
当对象的ACL发生变更时,系统在更新pg_class.relacl后,调用 CacheInvalidateRelcache 广播失效消息,使所有后端进程中的对应 Relation 缓存的rd_acl被标记为失效。后续访问时会重新从系统表加载最新ACL,确保权限检查的实时性。
四、ACL 的创建与修改
ACL 的修改通过 SQL 命令 GRANT 和 REVOKE 触发,其底层实现遵循“读取—修改—写回—刷新”的标准流程。针对不同数据库对象,系统提供了对应的处理函数(如 ExecGrant_Relation、ExecGrant_Proc 等),但核心逻辑高度一致。整个流程可分为两大阶段:构造新的 ACL 数组与持久化并刷新缓存。
4.1 构造新的 ACL 数组
在收到 GRANT 或 REVOKE 命令后,系统首先加载该对象当前的权限列表,并根据命令要求生成一份新的权限列表。该过程由通用函数aclupdate完成,主要包含以下步骤:
(1)加载现有权限列表
系统从目标对象所在系统表中读取其权限列表字段。若该字段为空,则根据对象类型和所有者动态生成一份“默认权限”列表作为初始值。默认权限通常包含所有者对该对象类型的全部权限,且无其他角色条目。加载后,系统将文本形式的权限列表转换为内存中的 AclItem 数组,每个 AclItem 记录一条授权信息(包含被授予者、授予者、权限位及对应的授权选项位)。
(2)处理 GRANT 操作
对于 GRANT 命令中指定的每个权限以及每个被授予者,在现有权限列表中查找是否存在相同 (grantee, grantor) 的记录。若存在:将该记录的权限位与本次授予的权限进行“按位或”运算,即追加新权限而不覆盖原有权限。若本次同时授予了授权选项,则同样通过“按位或”追加至高 16 位。若不存在:新建一个 AclItem,填入被授予者、授予者,并设置对应的权限位(低 16 位)和授权选项位(高 16 位)。
(3)处理 REVOKE 操作
对于 REVOKE 命令中指定的每个权限以及每个被授予者,查找对应 (grantee, grantor) 的记录。若存在,则从该记录的权限位中清除本次要回收的权限(通过位掩码的“与非”运算)。若回收的是授权选项,则仅清除高 16 位的对应位。如果清除后该记录的权限位(低 16 位)变为零,且授权选项位也为零,则说明该记录不再包含任何有效权限,将其从数组中删除。若命令指定了 CASCADE,系统还会递归回收那些依赖于被回收权限的其他授权(即通过该授权选项派生出的权限),确保权限依赖关系的完整性。
(4)优化与压缩
为提升后续权限检查的效率并保持存储的紧凑性,系统对新生成的 AclItem 数组执行两项优化:
排序:按 (grantee, grantor) 对数组元素进行排序,使得相同组合的条目连续存放。
合并:由于排序后相同组合的条目可能相邻,系统会将相邻的相同组合的权限位进行合并(“按位或”),删除冗余条目。
4.2 持久化并刷新缓存
新权限列表构造完成后,将其写回系统表,并通知所有可能持有旧缓存的进程进行更新。
(1)序列化存储
内存中的 AclItem 数组需要转换为系统表字段能够存储的文本格式。转换过程由 aclout 函数完成,它将每个 AclItem 表示为形如 "grantee=privs/grantor" 的字符串,多个条目用逗号分隔,整体作为数组文本。若最终列表为空(无任何授权记录),则存储为 NULL,语义上等同于“使用默认权限”。
(2)更新系统表记录
系统通过行级锁锁定目标对象的系统表行,防止并发修改。随后将序列化后的权限列表写回对应的 ACL 字段。更新完成后提交事务,使变更持久化。
(3)广播缓存失效
由于其他后端进程可能已在内存中缓存了该对象的旧权限列表,系统通过全局失效消息系统(CacheInvalidateRelcache 等接口)广播“该对象已变更”的通知。所有活跃的后端进程在接收到消息后,会将本地缓存中对应的对象描述符的 ACL 指针标记为无效,并在下次权限检查时重新从系统表加载最新列表。
(4)内存回收
更新操作完成后,原权限列表所占用的内存(通过 palloc 分配在 CacheMemoryContext 中)会被释放,其内存块归还至所属内存上下文的空闲链表中,供后续复用。新权限列表则通过 palloc 重新分配,并与对象描述符关联,准备迎接后续的权限检查请求。
4.3 依赖关系与级联处理
当回收某个角色的权限时,如果该角色曾经将其权限授权给其他角色(即拥有 GRANT OPTION),系统必须处理权限依赖关系。通过记录依赖对象来追踪这种“授权关系”。在 REVOKE 时,若指定了 CASCADE 选项,系统会递归查找所有直接或间接依赖于被回收权限的授权,一并撤销;若指定 RESTRICT,且存在依赖关系,则直接报错拒绝执行。
五、权限检查机制
权限检查是 ACL 系统中最频繁执行的操作,涉及 SQL 语句执行的每一个环节——从查询解析到执行计划生成,再到数据访问,每一步都需要验证当前角色是否具备操作目标对象的必要权限。不同对象类型提供了专门的权限检查函数(如 pg_class_aclcheck、pg_database_aclcheck、pg_proc_aclcheck 等),但核心检查逻辑高度统一。以表对象的权限检查为例,其执行流程可分为以下几个阶段:
(1)快速路径检查
系统首先判断当前角色的身份是否为超级用户。超级用户拥有数据库中所有对象的全部权限,无需进行任何 ACL 遍历。若 roleid 对应的角色被标记为超级用户,检查函数直接返回 ACLCHECK_OK。这一快速路径避免了后续所有权限判断开销,是权限检查性能优化的第一道关卡。
(2)获取 ACL 缓存
对于非超级用户,系统需要获取目标对象的权限列表。每个对象描述符内部维护了一个 ACL 缓存指针。该指针在首次访问时为空,此时系统从系统表中读取该对象的 ACL 字段,通过反序列化将其转换为内存中的 Acl 结构,并赋值给缓存指针。后续权限检查直接复用该缓存,无需重复读取系统表。
(3)逐项遍历 ACL 数组
获取到 Acl 数组后,系统遍历其中的每个 AclItem,对每个条目执行以下匹配逻辑:
被授予者匹配:检查当前条目的 ai_grantee 是否等于当前角色 ID,或者等于 ACL_ID_PUBLIC。PUBLIC 是一个特殊的“角色”,代表所有用户,其权限检查优先级低于显式授予给特定角色的权限。
权限位检查:若被授予者匹配成功,则检查该条目的权限位(低 16 位)是否包含本次请求的权限。若包含,则立即返回 ACLCHECK_OK。
遍历过程中,系统按照数组原有顺序进行匹配,一旦命中即提前终止,避免不必要的遍历开销。
(4)递归检查角色成员关系
若当前角色在 ACL 数组中未找到直接匹配的权限,系统不会立即判定为无权访问,而是进一步检查当前角色是否属于某个拥有权限的角色。通过 pg_auth_members 系统表记录角色之间的成员关系,支持多级继承。
检查函数递归地遍历当前角色的所有祖先角色,对每个祖先角色重复上述“遍历 ACL 数组”的逻辑。一旦某个祖先角色匹配成功,即返回成功。这一机制使得权限管理更加灵活——管理员可以将权限授予一组角色,再将具体用户加入该组,从而实现权限的集中管理。
(5)返回检查结果
若经过上述所有步骤均未找到有效权限,系统返回 ACLCHECK_NO_PRIV,表示当前角色不具备所需权限。上层调用者会根据返回结果决定是继续执行还是抛出权限错误。
(6)成员关系检查的优化
由于角色继承关系的递归检查可能涉及多次系统表查询,在会话级别对角色成员关系进行了缓存优化。系统维护了一个成员关系缓存(MembershipCache),当首次检查某角色的成员关系时,递归查询结果会被缓存起来,后续对同一角色的权限检查可直接命中缓存,大幅降低重复查询开销。此外,当角色成员关系发生变更时,系统会主动刷新相关缓存,确保权限判断的实时准确性。
六、ACL 的内存管理与持久化
6.1 内存分配
ACL 在内存中的生命周期与关联的对象描述符紧密绑定。系统通过 palloc 函数将 ACL 分配在CacheMemoryContext中,这是一个专用于缓存系统表数据的全局内存上下文。该上下文与 Relation 描述符的分配上下文一致,确保了 ACL 与描述符在内存管理上的统一性。
当对象描述符被关闭(如查询结束、表被删除)时,系统会释放描述符占用的内存,与之关联的 ACL 缓存也随之释放。若描述符因系统表缓存淘汰而被清理,ACL 缓存同样被回收。此外,当 ACL 发生变更时,旧缓存会被显式释放,新缓存重新分配,保证内存的及时回收与复用。
6.2 持久化存储
ACL 的持久化存储以数组类型为基础,存储在系统表的 aclitem[] 类型字段中。每个 aclitem 在磁盘上以定长结构存储,而在逻辑层面则通过文本表示进行输入输出。
多个 aclitem 构成一个数组,整体存储为数组类型,支持 TOAST 压缩。对象对应的系统表字段即为该数组类型,其值为 NULL 时表示“使用默认权限”。
总结
大云海山数据库依托存算分离架构、内核级改造、安全可靠 Ⅰ 级认证等核心优势,基于ACL权限控制机制,秉持最小权限原则,结合高效缓存与递归检查策略,在保障安全的前提下将对性能的影响降至最低,为用户提供灵活、高效的权限控制机制。






