3

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

语境

我在 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 表空间的数据库表空间有关。

问题

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

4

1 回答 1

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马上回来。然而,这可能会导致对用户采取更激烈的行动。最好您将在数据库中的每个表上创建一个插入前触发器,就像上面那样使用每日扫描。

用户仍将拥有 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;      

被锁定的用户可以在删除足够的数据以达到配额限制后自己调用此函数。

您可以添加更复杂的功能,例如列出每个用户的配额(而不是整体配额)并将实际使用量与该配额进行比较,RAISE NOTICE在超过 80% 的配额时发出插入触发器(这需要每个表有一个插入前触发器,可以由 postgres 用户在定期扫描新表时轻松完成,如果超过配额,可以使用相同的触发器来拒绝插入),每小时重复一次通知(所以记录最后一次通知发出的时间), ETC。

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

于 2014-03-15T11:51:33.260 回答