几千万的数据量,层级权限下的列表展示页查询应该怎么设计?

15 天前
 lying500

各位大佬,想请教一下这个关于查询用户拥有权限的数据的问题,感觉这个挺常见的需求

业务场景:

假设一个业务表,数据量在几千万级。 需要为这个表提供一个列表展示页,要求按创建时间倒序分页。 主要是权限问题导致查询慢: 1 、用户可以查看自己创建的数据。 2 、用户可以查看自己所属群组的数据。 3 、群组的权限是可继承的、层级的:如果一个用户属于某个上级群组,那么他自动拥有查看其所有下级、下下级...群组内数据的权限。

问题: 如果权限简单,比如只看自己的数据,查询非常简单: WHERE user_id = ? ORDER BY create_time DESC LIMIT N 这种查询用索引就好解决。

但如果加入群组权限,查询的逻辑就变成了: SELECT * FROM a_large_table WHERE user_id = ? OR group_id IN (用户所属群组以及所有下级群组的 ID 列表) ORDER BY create_time DESC LIMIT N

这个查询就比较慢了 比如假定结构是这样:

查询就变成了

SELECT *
FROM project
         JOIN `group` ON project.group_id = `group`.id
WHERE `group`.id IN (SELECT 用户关联的群组及其子群组 id)
   OR user_id = 20
ORDER BY project.created_at DESC
LIMIT 10;

这时候 (group_id, user_id, created_at) 也不好使;

问了 AI ,说了几个方案: 1 、应用层聚合/union user_id 和 group_id 的,建两个索引; 2 、冗余一张 用户能访问数据的表,直接查这个表; 3 、引入 es 之类的中间件;

想问一下实际大家是怎么处理的?

2896 次点击
所在节点    Java
36 条回复
micean
15 天前
权限并不是大问题
添加时间索引+业务必须提供时间区间
数据量大是绕不过的
snail88xin
15 天前
已经将近 5 年没干过后端了, 发表下自己的拙见, 大体逻辑就是空间换时间:
1. 复杂 sql 拆分成单条高性能 sql(加索引), 代码里边做数据合并处理, 这样做的好处是, 多条 sql 可同时查询且都是毫秒级, 权限逻辑、过滤规则都能写在业务层逻辑中, 便于维护, 但这样做会出现分页精度问题
2. 分页精度问题处理, 游标分页
3. 用户群组关系做缓存
vencent00
15 天前
给个邪修方案吧,位图存储 group_id,有多少最底层 group 就给多少字节,字节为 1 表示拥有这个 group 权限
soap0X
15 天前
自己能把握项目的话建议试着用 olap 库处理,增加个实践。我这有个老系统 2kw 数据量周周挂,我把握不了项目被迫在优化 sql 加限制
masterclock
15 天前
参考 zanzibar ?但也未必需要,性能在可接受范围内大概就先用着简单的方式
OpenFGA 、SpiceDB 等都还没有 materialize 的实现,自己搞有点搞不定,也未必需要
lbprivateacc
15 天前
倾向于方案 1 ,列表分页的话,一般只会看前几页吧,用 union all ,然后两个子 sql 都加上时间倒排+分页大小限制,最后 union all 再来一个倒排+分页(或者应用层处理)
cloudzhou
15 天前
尽量避免 or
在 group_id 可控的情况下(假设数量不大),为每个用户设定默认 group_id (和 user_id 一样即可)
举个例子:

设定 user_id 范围 [0-2**40] 也就是用户最大数量:1099511627776
剩下的 64 - 40 = 24 ,group_id 范围 [2**40-2**64]

40/24 自己定义,前 40 bit 给 user_id ,后 24 bit 给 group_id
这样
WHERE user_id = ? OR group_id IN (用户所属群组以及所有下级群组的 ID 列表)
转化为:
WHERE group_id IN (用户所属群组以及所有下级群组的 ID 列表,user_id)

user_id 同时也是每个用户默认的 group_id
siweipancc
15 天前
created_at 是不稳定排序,你还是要加 ID , 或者 ID 是 ULID UUID7 按 ID 排;
群组如果是闭包表 left join 消耗大内存就完事了
NoneUndefined
15 天前
时间加索引,然后 group 的逻辑可以用右 like ,这样能用索引覆盖到

最好业务上也加一下限制?比如很久以前的历史项目就从业务表清理掉?
huijiewei
15 天前
如果有个超大表和其他小表进行关联查询

建议先在小表上处理出来大表索引可以搜到的数据,然后再单独查大表。

大数据最忌讳 JOIN 来 JOIN 去的。
newtype0092
15 天前
去掉 user_id 统一成 group_id 才是正道。

@cloudzhou 这种规则维护性挺糟糕的,直接给每个用户默认新建一个 group 就好了,查询时加上自己的 group_id 就行,现在的逻辑都不用动。
MIUIOS
15 天前
在业务层拆分 sql 逻辑比较好,你这 in 估计很大
或者上 es ,我之前也遇到过这个问题,我最后上了 es ,缺点是真的重。
lizuoqiang
15 天前
我有个表结构数据和你挺像,有近 5 千万数据。
建立 user_id,created_at 联合索引 idx_user_create
查询 select * from t force index(idx_user_create) where user_id in (a,b,c...) order by created_at desc limit 100 offset 110000
耗时 400ms 左右,如果是后台应用这个时间应该可以接受
cloudzhou
15 天前
@newtype0092 对,你说的合理,我的意思就是每个 user_id 有个 group_id ,不需要强行一样
hack 手段虽然取巧,长期看不适合,需要维护潜在规则(虽然难度不大,group id 从预定最大 user_id 起始开始就好了)
Pythoner666666
14 天前
@lying500 7 楼的方案没有问题,索引调整一下就哈了。 建联合索引 uid,created_at ,然后查询的时候 FORCE INDEX (uid_created_at)
chaoschick
14 天前
但如果加入群组权限,查询的逻辑就变成了:SELECT * FROM a_large_table WHERE user_id = ? OR group_id IN (用户所属群组以及所有下级群组的 ID 列表) ORDER BY create_time DESC LIMIT N

你这个例子里面的 user_id 是多余的条件吧 因为 group_id 后面已经跟了 用户所属群组了
如果某个记录是用户自己创建的 那么这条记录里的 user_id 与 group_id 就是从当前用户上获取的吧

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1164842

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX