电科金仓权限管理实战:那些让我加班到凌晨的权限“陷阱”
电科金仓权限管理实战:那些让我加班到凌晨的权限“陷阱”
上周五晚上10点,我正准备下班,开发组长小王火急火燎地跑过来:“哥,我们的报表系统崩了,用户说查不到数据!”我一看,又是权限问题——视图访问授权没做对。这已经是我们迁移到电科金仓后遇到的第N个权限问题了。今天,我把这些坑一个个挖出来,填平了给大家看。
开头:那个让我加班到凌晨的周五
说实话,我干了八年数据库运维,从Oracle到MySQL,啥场面没见过。但去年公司决定用电科金仓KES替换Oracle,我在权限管理上真是栽了不少跟头。
就说上周五那个事吧。我们的报表系统里有个核心视图,业务部门每天都要用。结果那天下午,突然所有用户都报“权限不足”。我查了半天,发现是有人误操作,把schema权限给改了。
这个经历让我意识到:金仓的权限体系很强大,但也很“较真”,一个细节没注意,整个系统都可能出问题。今天我就讲讲三个最常见的权限“陷阱”,都是我们团队用真金白银换来的经验。
陷阱一:视图访问授权——你以为授权了,其实没有
问题现场:“这个视图我能建,为啥别人不能查?”
我们有个数据分析师小张,他在自己的schema里建了个视图,然后想让其他部门的同事也能查。他觉得很简单:“不就是给个查询权限嘛!”结果操作完,同事还是报错。
我让他演示一下操作过程:
-- 小张(ud用户)建了个视图
\c - ud
CREATE VIEW ud.sales_view AS
SELECT * FROM sales_data WHERE region = '华东';
-- 他觉得自己已经授权了
GRANT SELECT ON ud.sales_view TO report_user;
看起来没问题对吧?但report_user还是查不了。错误信息是:
ERROR: permission denied for schema ud
问题根源:schema权限是道“门”
我告诉小张:“你给了人家进房间的钥匙(视图权限),但没开门啊!”
在金仓里,schema就像一个个房间。你要进房间拿东西(查询视图),需要两把钥匙:
- 房间的门禁卡(schema的USAGE权限)
- 具体物品的取用权(视图的SELECT权限)
正确操作:两步授权法
我教小张正确的授权流程:
第一步:先开门(授权schema访问)
-- 需要schema的owner或者有授权权限的人来操作
\c - system -- 用system用户,因为schema owner是system
-- 给report_user发门禁卡
GRANT USAGE ON SCHEMA ud TO report_user;
第二步:再授权物品(授权视图查询)
-- 小张可以自己操作,因为他是视图owner
\c - ud
GRANT SELECT ON ud.sales_view TO report_user;
这两步都做完,report_user才能正常查询。
进阶技巧:批量授权和角色管理
后来我们发现,这种需求很常见。与其每次都手动操作,不如建立一套权限管理体系。
方案一:创建只读角色
-- 创建一个专门用于查询的角色
CREATE ROLE report_viewer NOLOGIN;
-- 批量授权所有视图
GRANT SELECT ON ALL TABLES IN SCHEMA ud TO report_viewer;
-- 注意:这个ALL TABLES包括视图!
-- 把角色赋给用户
GRANT report_viewer TO report_user1, report_user2, report_user3;
方案二:设置默认权限
如果我们希望未来新建的视图也能自动授权,可以设置默认权限:
-- 设置默认权限,以后ud用户在ud schema下建的所有表/视图,
-- report_viewer角色都能查
ALTER DEFAULT PRIVILEGES FOR USER ud IN SCHEMA ud
GRANT SELECT ON TABLES TO report_viewer;
这个功能特别有用,设好之后就不用每次手动授权了。
真实案例:我们搞砸过一次
有一次我们给一个新员工授权,忘了给schema权限。结果这位同事试了半天都查不了数据,还以为系统坏了,差点提了紧急工单。
后来我们做了个授权检查清单,每次授权都按这个来:
- 检查用户是否需要schema权限
- 检查用户是否需要具体对象权限
- 验证授权是否生效
- 通知用户测试
陷阱二:权限简写——那些神秘字母的含义
问题现场:“arwdDxt是啥?外星语吗?”
刚用金仓的时候,我最头疼的就是看权限信息。比如这样的:
ud=arwdDxt/ud
role01=r/ud
我问团队里的小李:“你能看懂这啥意思吗?”小李一脸懵:“王哥,这比摩斯密码还难懂啊!”
解密:每个字母都有含义
其实这是金仓的权限简写系统。我后来专门研究了文档,搞明白了每个字母的意思:
- a - INSERT(追加)
- r - SELECT(读取)
- w - UPDATE(写入)
- d - DELETE(删除)
- D - TRUNCATE(清空)
- x - REFERENCES(外键引用)
- t - TRIGGER(触发器)
所以arwdDxt就是拥有所有权限,r就是只有查询权限。
实战:如何查看和分析权限
查看表权限:
-- 最简单的查法
\dp 表名
-- 或者用SQL查
SELECT
relname AS 表名,
relacl AS 权限信息
FROM sys_class
WHERE relname = 'sales_data';
查看schema权限:
SELECT
nspname AS schema名,
sys_catalog.sys_get_userbyid(nspowner) AS 所有者,
nspacl AS 权限信息
FROM sys_namespace
WHERE nspname = 'ud';
查看数据库权限:
SELECT
datname AS 数据库名,
sys_catalog.sys_get_userbyid(datdba) AS 所有者,
datacl AS 权限信息
FROM sys_database;
小技巧:写个解析函数
为了让团队都能看懂,我写了个权限解析函数:
CREATE OR REPLACE FUNCTION decode_privileges(priv_text TEXT)
RETURNS TABLE(privilege_name TEXT, privilege_desc TEXT) AS $$
DECLARE
priv_mapping TEXT[][] := ARRAY[
['a', 'INSERT(插入数据)'],
['r', 'SELECT(查询数据)'],
['w', 'UPDATE(更新数据)'],
['d', 'DELETE(删除数据)'],
['D', 'TRUNCATE(清空表)'],
['x', 'REFERENCES(外键引用)'],
['t', 'TRIGGER(触发器操作)']
];
i INT;
ch CHAR;
BEGIN
-- 遍历权限字符串的每个字符
FOR i IN 1..length(priv_text) LOOP
ch := substring(priv_text FROM i FOR 1);
-- 查找对应的权限描述
FOR j IN 1..array_length(priv_mapping, 1) LOOP
IF priv_mapping[j][1] = ch THEN
privilege_name := ch;
privilege_desc := priv_mapping[j][2];
RETURN NEXT;
EXIT;
END IF;
END LOOP;
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- 使用示例
SELECT * FROM decode_privileges('arwd');
现在团队里谁看不懂权限简写,就跑一下这个函数,一目了然。
经验教训:权限审计很重要
我们曾经遇到过权限泄露问题。有人给一个临时账户授予了过大权限,后来忘了回收。幸亏我们定期做权限审计:
-- 每月一次的权限审计脚本
SELECT
'高风险:过度授权' AS 问题类型,
grantee::regrole AS 用户,
table_schema || '.' || table_name AS 对象,
privilege_type AS 权限
FROM information_schema.role_table_grants
WHERE grantee NOT IN ('system', 'sso', 'sao') -- 排除管理员
AND privilege_type IN ('DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER')
AND table_schema NOT LIKE 'pg_%'
AND table_schema != 'information_schema'
ORDER BY grantee, table_schema, table_name;
陷阱三:VACUUM权限——普通用户的“禁区”
问题现场:“我连清理表都不行吗?”
这是我们运维小刘遇到的问题。他发现某个表膨胀得厉害,想手动VACUUM一下,结果:
\c - app_user
VACUUM sales_data;
-- 结果:
WARNING: skipping "sales_data" --- only table or database owner can vacuum it
小刘很郁闷:“我好歹也是这个表的owner,怎么连清理都不行?”
真相:VACUUM是个特权操作
我告诉小刘:“VACUUM在金仓里是个特殊操作,普通用户确实干不了。”
为什么呢?因为VACUUM涉及到底层存储结构的调整,有一定风险。金仓设计时,只允许两种人执行VACUUM:
- 表的owner
- 超级用户(superuser)
解决方案:几种变通方法
方法一:让表的owner来执行
这是最简单的办法。如果app_user是表的owner,理论上可以执行。但如果还是不行,可能是因为有其他限制。
方法二:临时提权(谨慎使用)
-- 用system用户临时授权
\c - system
GRANT system TO app_user; -- 让app_user临时拥有system权限
-- app_user执行VACUUM
\c - app_user
VACUUM sales_data;
-- 用完马上回收
\c - system
REVOKE system FROM app_user;
方法三:设置超级用户(不推荐)
ALTER USER app_user WITH SUPERUSER;
但这个方法太危险了!一旦给用户超级权限,他就能为所欲为,包括删除整个数据库。
我们的实践:建立VACUUM管理流程
我们公司最后定了一套规矩:
- 普通用户不准手动VACUUM:全部交给自动VACUUM机制
- 紧急情况走流程:确实需要手动VACUUM时,提工单给DBA团队
- DBA统一操作:每周一早上,DBA检查表膨胀情况,统一执行VACUUM
我们还写了个监控脚本,自动发现需要VACUUM的表:
-- 监控表膨胀的脚本
CREATE OR REPLACE FUNCTION check_table_bloat()
RETURNS TABLE(
schema_name TEXT,
table_name TEXT,
row_count BIGINT,
dead_row_count BIGINT,
bloat_ratio NUMERIC(5,2),
need_vacuum BOOLEAN
) AS $$
BEGIN
RETURN QUERY
SELECT
schemaname::TEXT,
tablename::TEXT,
n_live_tup,
n_dead_tup,
CASE
WHEN n_live_tup = 0 THEN 0
ELSE ROUND(n_dead_tup * 100.0 / n_live_tup, 2)
END AS bloat_ratio,
(n_dead_tup > 1000 AND n_dead_tup > n_live_tup * 0.2) AS need_vacuum
FROM sys_stat_user_tables
WHERE n_dead_tup > 0
ORDER BY bloat_ratio DESC;
END;
$$ LANGUAGE plpgsql;
-- 每天检查一次
SELECT cron.schedule('check-bloat-daily', '0 2 * * *',
$$INSERT INTO vacuum_log
SELECT *, CURRENT_TIMESTAMP FROM check_table_bloat()
WHERE need_vacuum = true$$);
血的教训:一次VACUUM事故
去年我们有个新来的DBA,想提高性能,给一个业务用户临时加了超级权限执行VACUUM。结果完事后忘了回收权限。
一个月后,这个用户误操作删除了一个重要表。虽然最后从备份恢复了,但业务停了两个小时,损失不小。
从此以后,我们立下铁规:绝不给人超级权限,VACUUM必须走流程。
权限管理最佳实践:我们总结的“三要三不要”
通过这三年的实践,我们总结了金仓权限管理的“三要三不要”:
三要
1. 要分层授权
-- 不好的做法:一次性给所有权限
GRANT ALL ON SCHEMA sales TO sales_user;
-- 好的做法:分层授权
GRANT USAGE ON SCHEMA sales TO sales_user; -- 基础权限
GRANT SELECT ON sales.customer TO sales_user; -- 具体表权限
GRANT INSERT, UPDATE ON sales.order TO sales_user; -- 特定操作的权限
2. 要用角色管理
不要直接给用户授权,而是通过角色:
-- 创建角色
CREATE ROLE sales_read_only;
CREATE ROLE sales_read_write;
-- 给角色授权
GRANT SELECT ON ALL TABLES IN SCHEMA sales TO sales_read_only;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA sales TO sales_read_write;
-- 把角色给用户
GRANT sales_read_only TO user1;
GRANT sales_read_write TO user2;
3. 要定期审计
每月做一次权限审计:
-- 审计脚本
CREATE TABLE permission_audit_log (
audit_id SERIAL PRIMARY KEY,
audit_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
object_type TEXT,
object_name TEXT,
grantee TEXT,
privileges TEXT,
auditor TEXT
);
-- 每月1号自动运行
SELECT cron.schedule('permission-audit', '0 0 1 * *', $$
INSERT INTO permission_audit_log
(object_type, object_name, grantee, privileges, auditor)
SELECT
'TABLE',
schemaname || '.' || tablename,
grantee,
privilege_type,
CURRENT_USER
FROM information_schema.role_table_grants
WHERE grantee NOT IN ('system', 'sso', 'sao');
$$);
三不要
1. 不要给人超级权限
-- 千万别这么干!
ALTER USER app_user WITH SUPERUSER;
2. 不要用ALL PRIVILEGES
-- 太危险了!
GRANT ALL PRIVILEGES ON DATABASE app_db TO app_user;
-- 应该细化授权
GRANT CONNECT ON DATABASE app_db TO app_user;
GRANT CREATE ON SCHEMA app_schema TO app_admin;
3. 不要忘记回收权限
-- 员工离职时一定要做
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA sales FROM former_employee;
REVOKE ALL PRIVILEGES ON SCHEMA sales FROM former_employee;
REVOKE CONNECT ON DATABASE app_db FROM former_employee;
DROP USER former_employee;
写在最后:权限管理是门学问
从Oracle转到电科金仓,我在权限管理上确实花了些时间适应。但说实话,适应之后发现金仓的权限体系设计得挺合理的——严格但清晰,复杂但可控。
现在回想那些加班到凌晨的日子,虽然辛苦,但值得。因为我们建立了一套安全、可控、易管理的权限体系。
如果你也在用金仓,或者在考虑用金仓,我建议你:
- 一定要理解三权分立:system、sso、sao各司其职
- 一定要从简单开始:先给最小权限,不够再加
- 一定要有文档:每个权限变动都要记录
- 一定要定期审查:权限就像房间,久了不打扫会乱
最后,如果你在权限管理上遇到了问题,或者有更好的实践经验,欢迎到金仓技术探索社区(https://kingbase.com.cn/explore)交流。那里有很多实战案例和技术文章,说不定能帮你少走弯路。
权限管理这条路,我们走过来了。虽然踩过坑、加过班,但最终建成了一套让业务安心、让运维省心的权限体系。希望我们的经验,能帮你走得更稳、更快。

浙公网安备 33010602011771号