我在让查询计划程序为启用行级安全 (RLS) 的表编写好的计划时遇到了一些麻烦。似乎只需要从启用行级安全性的表连接到启用非行级安全性的表即可强制执行错误的计划,即使规划器应该能够使用两个表上的适当索引。
有没有办法帮助规划师解决这个问题?或者当涉及 RLS 时某些统计数据不可用?
我尝试USING (TRUE)
为不需要 RLS 的表启用 RLS(添加一个广泛开放的策略),这与不在该表上包含策略具有相同的效果。
DROP SCHEMA IF EXISTS foo CASCADE;
CREATE SCHEMA foo;
CREATE TABLE foo.bar AS
SELECT generate_series(1,10000000) AS id, md5(random()::text) AS descr, random() * 5 + 1 AS licflag;
CREATE TABLE foo.baz AS
SELECT generate_series(1,10000000) AS id, md5(random()::text) AS descr, random() * 5 + 1 AS licflag;
CREATE UNIQUE INDEX ON foo.bar (id);
CREATE INDEX ON foo.bar (licflag);
CREATE UNIQUE INDEX ON foo.baz (id);
CREATE INDEX ON foo.baz (licflag);
ANALYZE foo.bar;
ANALYZE foo.baz;
ALTER TABLE foo.bar ENABLE ROW LEVEL SECURITY;
--ALTER TABLE foo.baz ENABLE ROW LEVEL SECURITY;
DROP ROLE IF EXISTS restricted;
CREATE ROLE restricted NOINHERIT;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA foo FROM restricted;
GRANT restricted to current_user;
GRANT USAGE ON SCHEMA foo TO restricted;
GRANT SELECT ON ALL TABLES IN SCHEMA foo TO restricted;
CREATE POLICY restrict_foo ON foo.bar
FOR SELECT TO restricted
USING (licflag < 3);
/*
CREATE POLICY restrict_foo ON foo.baz
FOR SELECT TO restricted
USING (TRUE);
*/
EXPLAIN ANALYZE
SELECT *
FROM foo.bar f1
JOIN foo.baz f2 ON f1.id = f2.id
WHERE f2.id BETWEEN 500 AND 12000
AND f1.licflag < 3;
SET ROLE restricted;
EXPLAIN ANALYZE
SELECT *
FROM foo.bar f1
JOIN foo.baz f2 ON f1.id = f2.id
WHERE f2.id BETWEEN 500 AND 12000;
结果是
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.87..87677.34 rows=4668 width=90) (actual time=0.091..45.337 rows=4622 loops=1)
-> Index Scan using baz_id_idx on baz f2 (cost=0.43..471.90 rows=11573 width=45) (actual time=0.042..4.496 rows=11501 loops=1)
Index Cond: ((id >= 500) AND (id <= 12000))
-> Index Scan using bar_id_idx on bar f1 (cost=0.43..7.53 rows=1 width=45) (actual time=0.003..0.003 rows=0 loops=11501)
Index Cond: (id = f2.id)
Filter: (licflag < '3'::double precision)
Rows Removed by Filter: 1
Planning time: 1.300 ms
Execution time: 45.826 ms
(9 rows)
SET
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------
Hash Join (cost=569.62..273628.35 rows=4227 width=90) (actual time=8.317..2074.996 rows=4558 loops=1)
Hash Cond: (f1.id = f2.id)
-> Seq Scan on bar f1 (cost=0.00..218457.95 rows=3967891 width=45) (actual time=0.016..1616.577 rows=3998388 loops=1)
Filter: (licflag < '3'::double precision)
Rows Removed by Filter: 6001612
-> Hash (cost=436.47..436.47 rows=10652 width=45) (actual time=8.033..8.033 rows=11501 loops=1)
Buckets: 16384 Batches: 1 Memory Usage: 1027kB
-> Index Scan using baz_id_idx on baz f2 (cost=0.43..436.47 rows=10652 width=45) (actual time=0.026..4.871 rows=11501 loops=1)
Index Cond: ((id >= 500) AND (id <= 12000))
Planning time: 0.305 ms
Execution time: 2075.371 ms
(11 rows)
我上线了
psql (9.5.3, server 9.5.4)
更新 1:
我在 WHERE 子句中使用 RLS 谓词运行查询,例如
EXPLAIN ANALYZE
SELECT *
FROM foo.bar f1
JOIN foo.baz f2 ON f1.id = f2.id
WHERE f1.id BETWEEN 500 AND 12000
AND f1.licflag < 3;
它产生了一个更好的查询计划。但是当我删除这个额外的谓词时,计划者保留了更好的计划。这让我认为统计数据有问题..有人知道如何在不重置整个数据库的统计数据的情况下手动触发统计数据更新吗?现在自己浏览 Postgres 文档...
更新 2:
尝试设置全局和表级统计限制无济于事。能够使用子选择而不是使用类似查询的连接来获得正确的查询计划,因此可以使用该技术作为解决方法。