阻止用户在默认表空间中创建表

时间:2014-01-17 18:24:50

标签: postgresql tablespace

我有一个问题,到目前为止还没有找到任何线索。我会尽力解释它,但请随时询问更多细节!

上下文

我在Windows上使用Postgres 9.2.4,我需要为每个用户实施某种配额管理。

据我所知,没有这样的内置功能,大多数答案都指向使用文件系统的配额管理功能。

只有一个数据库,每个用户都有自己的架构。

我采取的方法包括通过使用不同的表空间为每个用户分隔数据文件,每个用户一个,用户是其表空间的所有者(因此我可以在每个用户上应用配额配置)文件夹基础)。

这让我想到了我面临的问题......

问题

在创建表时,用户可以选择 pg_default 表空间来存储数据。

为了增加我的困惑,如果稍后我将表空间更改为用户拥有的表空间,然后尝试将其切换回pg_default表空间,则会抛出权限被拒绝错误。

这里澄清序列是一些示例代码:

-- Creates the table in the default tablespace
CREATE TABLE test_schema.test_table ( ) 
TABLESPACE pg_default;

-- Changes the tablespace to the one owned by the user
ALTER TABLE test_schema.test_table
SET TABLESPACE user_tablespace;

-- Tries to set back the pg_default tablespace (throws permission denied to pg_default tablespace)
ALTER TABLE test_schema.test_table
SET TABLESPACE pg_default;

所有这些命令都是在没有管理权限的情况下使用用户登录执行的。 pg_default表空间由postgres登录(管理帐户)拥有。

我的猜测是它与数据库表空间有关,数据库表空间设置为使用pg_default表空间。

问题

可以将用户限制为仅在其拥有的表空间中创建对象吗?

1 个答案:

答案 0 :(得分:2)

如果你使用磁盘配额,那么你会给自己做很多工作。事实上,PostgreSQL中有一个近似的解决方案,只需要一些小修补,不需要创建大量的表空间(模式仍然是一个好主意,让每个用户都有自己的命名空间)。

函数pg_total_relation_size(regclass)为您提供了用于表的总磁盘空间,包括其索引和TOAST表。所以扫描pg_class并总结:

CREATE VIEW user_disk_usage AS
  SELECT r.rolname, SUM(pg_total_relation_size(c.oid)) AS total_disk_usage
  FROM pg_class c, pg_roles r
  WHERE c.relkind = 'r'
    AND c.relowner = r.oid
  GROUP BY c.relowner;

这将为您提供每个所有者使用的总磁盘空间,而不管表位于何处。它在此处作为视图定义呈现,供以下使用。

要以合理准确的方式完成这项工作,您需要定期VACUUM ANALYZE您的数据库。如果您的流量较低(例如每天凌晨3点至凌晨5点,或星期日)运行它,那么使用带有用户postgres的预定作业。为执行VACUUM然后配额检查的作业创建一个函数:

CREATE FUNCTION user_quota_check() RETURNS void AS $$
DECLARE
  user_data record;
BEGIN
  -- Vacuum the database to get accurate disk use data
  VACUUM FULL ANALYZE;

  -- Find users over disk quota
  FOR user_data IN SELECT * FROM user_disk_usage LOOP
    IF (user_data.total_disk_usage > <<your quota>>) THEN
      EXECUTE 'REVOKE CREATE ON SCHEMA ' || <<user''s schema name>> || ', PUBLIC FROM ' || user_data.rolname;
      -- REVOKE INSERT privileges too, unless you work with BEFORE INSERT triggers on all tables
    END IF;
  END LOOP;
END; $$ LANGUAGE plpgsql;
REVOKE ALL ON FUNCTION user_quota_check() FROM PUBLIC;

如果所有者超过配额,您可以在所有相关模式上REVOKE CREATE,通常只分配给用户和公共模式的模式,这样就不会创建新表。您还应该在所有表格上REVOKE INSERT,但这很容易被规避,因为所有者可以GRANT INSERT回来。然而,这可能导致对用户采取更激烈的行动。优选地,您将在数据库中的每个表上创建一个before insert触发器,使用与上面一样的每日扫描。

用户仍然具有SELECT权限,因此他/她仍然可以访问数据。更有趣的是,DELETE和TRUNCATE将允许用户释放磁盘空间并修复锁定。然后可以使用类似于上述功能的东西重新设置权限:

CREATE FUNCTION reclaim_disk_space() RETURNS void AS $$
DECLARE
  disk_use bigint;
BEGIN
  -- Vacuum current_user's tables.
  -- Slow and therefore adequate punishment for going over quota.
  VACUUM FULL VERBOSE ANALYZE;

  -- Now re-instate privileges if enough space was reclaimed.
  SELECT total_disk_usage INTO disk_use
  FROM user_disk_usage
  WHERE rolname = session_user;
  IF (disk_use < <<your quota>>) THEN
    EXECUTE 'GRANT CREATE ON SCHEMA ' || <<user''s schema name>> || ', PUBLIC TO ' || user_data.rolname;
    -- GRANT INSERT privileges too, unless you work with BEFORE INSERT triggers on all tables
    RAISE NOTICE 'Disk use under quota limit. Privileges restored.';
  ELSE
    RAISE NOTICE 'Still using too much disk space. Free up more space.';
  END IF;
END; $$ LANGUAGE plpgsql;      

锁定的用户在删除了足够的数据以达到配额限制后,可以自行调用此功能。

您可以添加更复杂的功能,例如列出每个用户的配额(而不是总配额),并将实际使用情况与该配额进行比较,当超过80%时,在插入触发器上发出RAISE NOTICE of quota(这要求每个表都有一个before insert触发器,postgres用户可以在新表的常规扫描中轻松完成,如果超过配额,可以使用相同的触发器拒绝插入),每小时重复一次通知(所以在发出最后通知时记录),等等。

此解决方案是近似值,因为未实时检查配额。这是可能的(在每个插入上运行user_quota_check(),修改为仅检查session_user的表)但最有可能过多的开销使它变得有趣。隔夜运行user_quota_check()以进行配额的日常管理。并且在白天手动鞭打任何使用太多空间的用户。