【问题标题】:ACL like matching in SQL类似 SQL 中的 ACL 匹配
【发布时间】:2019-01-28 22:46:04
【问题描述】:

我正在尝试将类似 ACL 的数据存储到表中,并检查特定路径是否与任何存储的模式匹配。

我在 MySQL 和 PostgreSQL 上都进行了测试。

有我的表和(BTREE)索引:

create table acl (id serial, pattern text, block bool);
create index acl_pattern on acl(pattern);

我首先尝试像这样存储通配符,它​​可以工作,但是我找不到使用索引的方法,我认为这是不可能的:

insert into acl values (default, '/public/%', false);
insert into acl values (default, '/admin/%', true);
select * from acl where '/public/hello' like pattern;

由于大多数(如果不是全部)模式只是前缀,我试图通过这样做来避免使用通配符,但我也不能使用索引:

insert into acl values (default, '/public/', false);
insert into acl values (default, '/admin/', true);

// PostgreSQL
test=# explain analyze select block from acl where pattern = substring('/public/blabla', 0, length(pattern)+1);
                                                   QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
 Seq Scan on acl  (cost=10000000000.00..10000000001.04 rows=1 width=1) (actual time=0.058..0.059 rows=1 loops=1)
   Filter: (pattern = "substring"('/public/blabla'::text, 0, (length(pattern) + 1)))
   Rows Removed by Filter: 1
 Planning Time: 0.074 ms
 Execution Time: 0.085 ms
(5 rows)

test=# explain analyze select block from acl where pattern = 'test';
                                                   QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
 Index Scan using acl_pattern on acl  (cost=0.13..8.14 rows=1 width=1) (actual time=0.039..0.039 rows=0 loops=1)
   Index Cond: (pattern = 'test'::text)
 Planning Time: 0.147 ms
 Execution Time: 1.063 ms
(4 rows)

// MySQL
mysql> explain select block from acl where pattern = left('/public/blabla', length(pattern));
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | acl   | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |    50.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select block from acl where pattern = "hello";
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | acl   | NULL       | ref  | acl_pattern   | acl_pattern | 1019    | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+

当我用静态值替换比较的正确值时,索引得到正确使用,看起来像调用函数或在正确值上使用模式字段使索引的使用无效?

我还尝试使用 CockroachDB 进行比较(使用与 PostgreSQL 完全相同的查询),我得到了完全相同的行为:

root@:26257/defaultdb> explain select block from acl where pattern = substring('/public/blabla', 0, length(pattern)+1);
    tree    | field  |                          description
+-----------+--------+---------------------------------------------------------------+
  render    |        |
   └── scan |        |
            | table  | acl@primary
            | spans  | ALL
            | filter | pattern = substring('/public/blabla', 0, length(pattern) + 1)

root@:26257/defaultdb> explain select block from acl where pattern = 'hello';
       tree       | field |         description
+-----------------+-------+-----------------------------+
  render          |       |
   └── index-join |       |
        ├── scan  |       |
        │         | table | acl@acl_pattern
        │         | spans | /"hello"-/"hello"/PrefixEnd
        └── scan  |       |
                  | table | acl@primary

【问题讨论】:

  • 索引不会用于单行。用几千行填充表并再次运行解释。
  • 那为什么我用pattern = 'hello'的时候能正确使用呢?
  • 你不能从单排表的测试中得出任何结论。
  • 我在 PostgreSQL 表中用 100.000 行(并运行分析)重新进行了相同的测试,同样的问题。

标签: mysql sql database postgresql indexing


【解决方案1】:

似乎无法使用索引,因为右侧表达式取决于pattern(因此需要从表中读取)。

假设您可以确定模式的最小长度(例如 6 个字符),您可以尝试以下操作:

create index acl_pattern on acl(left(pattern, 6));

select * 
from acl 
where left(pattern, 6) = left('/public/something', 6) and '/public/something' like pattern

【讨论】:

    【解决方案2】:

    要使用 LIKE,您的索引缺少 text_pattern_ops 运算符。 Postgres 在字符方面有点特别,它处理 btree 的方式意味着行为会因设置而异,因此您可能需要阅读此内容。 TLDR 你的索引应该像这样使用 LIKE:

    create index acl_pattern on acl(pattern text_pattern_ops);

    https://www.postgresql.org/docs/11/indexes-opclass.html

    另一个问题是 Postgres 有一个查询计划器,所以如果你的表只有 2 行,它不会考虑首先检查索引是否值得,因为索引可能会简单地告诉它引用无论如何,这两行都在的表。

    【讨论】:

    • 我已将 100.000 放在桌子上以获得更多数据,但即使在用您的索引替换我的索引后,我也会得到相同的行为。
    • 提示是假的。索引不支持like右侧使用的列。
    • 抱歉,我没有看到查询使用 like 与列值 - 在这种情况下,百分比将按字面意思而不是通配符
    【解决方案3】:

    (从 MySQL 的角度来看。我不会说 postgres。)

    pattern = left('/public/blabla', length(pattern))
    

    -->

    SELECT ...
        FROM ...
        WHERE pattern <= '/public/blabla'
        ORDER BY pattern DESC
        LIMIT 1
    

    这将使您在 O(1) 时间内获得第一个匹配行。或者它会给你一些不匹配的东西。现在,让我们检查一下:

    SELECT ...
        FROM ( SELECT --- as above ) AS x
        WHERE pattern = LEFT('/public/blabla', CHAR_LENGTH(pattern))
    

    这将提供 1 行或空集。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-18
      • 2013-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多