我正在使用这种布局(Firebird 2.1)开发呼叫/服务台软件:
通过选择一个区域和该区域中的一种呼叫类型来打开呼叫(表呼叫、区域和类型)
为用户分配了配置文件,其中说明了他们可以查看或编辑呼叫的区域和类型(表用户和表配置文件)
根据配置文件,用户可能能够查看或编辑某个区域的所有类型(表 profile_areas),或者只查看或编辑选定类型的呼叫(表 profile_types)
用户可能有特殊权限,除了能够查看他们的配置文件中分配的区域外,还可以查看他们打开的任何呼叫(表配置文件上的布尔列)
我发布了一个精简的、列重命名的主要结构版本。我不认为我将能够发布真正的表格,它跨越了 300 多个字段和更多的 FK 表格。
这是关于调用和用户权限的最低限度的结构。
CREATE TABLE CALLS (
CALLID INTEGER, /* PK */
AREAID INTEGER, /* FK ON TABLE AREAS */
TYPEID INTEGER, /* FK ON TABLE TYPES */
USERID_OPENED_BY, /* FK ON TABLE USERS */
STATUS CHAR(1)
);
CREATE TABLE AREAS (
AREAID INTEGER, /* PK */
AREA_NAME VARCHAR(50),
);
CREATE TABLE TYPES (
TYPEID INTEGER, /* PK */
AREAID INTEGER, /* FK ON TABLE AREAS */
TYPE_NAME VARCHAR(50),
);
CREATE TABLE USERS (
USERID INTEGER, /* PK */
PROFILEID INTEGER, /* FK ON TABLE PROFILES */
USER_NAME VARCHAR(50),
);
CREATE TABLE PROFILES (
PROFILEID INTEGER, /* PK */
PROFILE_NAME VARCHAR(50),
VIEW_ALL_CALLS_OPENED CHAR(1) /* if true, user can always view any calls he opened, regardless of area or type */
);
CREATE TABLE PROFILES_AREAS (
PAREA_ID INTEGER, /* PK */
PROFILEID INTEGER, /* FK ON TABLE PROFILES */
AREAID INTEGER (FK),
CAN_VIEW_AREA CHAR(1), /* can view any calls on this area, regardless of types */
CAN_EDIT_AREA CHAR(1) /* can edit any calls on this area, regardless of types */
);
CREATE TABLE PROFILES_TYPES (
PTYPE_ID INTEGER, /* PK */
PROFILEID INTEGER, /* FK ON TABLE PROFILES */
TYPEID INTEGER, /* FK ON TABLE TYPES */
CAN_VIEW_TYPE CHAR(1), /* can view any calls of this type */
CAN_EDIT_TYPE CHAR(1) /* can edit any calls of this type */
);
我们开始看到我们的第一批客户达到了 10+ 百万次呼叫,并且任何主要的简单查询都开始变得非常缓慢。
在分析查询计划时,所有似乎都正确索引,但索引读取的数量几乎总是显示 1000 万左右,即使查询总共返回 5 个左右的结果。
问题似乎是我们没有成功地使用连接来构建 where 子句,因为配置文件可能有许多不同的变体,这反过来又产生了我们必须处理的大量不同的 OR 子句。
最坏的情况是:
1.用户可以查看他打开的所有电话
2.用户可以查看部分区域,但不能查看全部
3.用户可以查看部分类型,但不是全部
这给我们留下了这样的东西(假设用户ID是“1”):
SELECT CALLID FROM CALLS
WHERE
CALLS.USERID_OPENED_BY = 1 /* .User can view all calls he opened */
OR (
CALLS.AREAID IN (1,2,3) /* areas the user can view, in his profile. we tried using a subselect here and things just went from bad to much, much worse */
OR
CALLS.TYPEID IN (1,2,3) /* types the user can view, in his profile. we tried using a subselect here and things just went from bad to much, much worse */
)
而那种 where 子句正在扼杀性能。
有人建议我们尝试将 OR 拆分为不同的查询并与 union 相加,但许多其他因素使得这非常麻烦。
理想情况下,我们试图阻止我们的客户使用如此广泛的配置文件权限,但趋势似乎是对配置文件类型的更新和更模糊的需求(这就是实施“查看他打开的所有呼叫”的原因,因为例子)。
我们应该遵循什么更好的策略?