2

我正在使用这种布局(Firebird 2.1)开发呼叫/服务台软件:

  1. 通过选择一个区域和该区域中的一种呼叫类型来打开呼叫(表呼叫、区域和类型)

  2. 为用户分配了配置文件,其中说明了他们可以查看或编辑呼叫的区域和类型(表用户和表配置文件)

  3. 根据配置文件,用户可能能够查看或编辑某个区域的所有类型(表 profile_areas),或者只查看或编辑选定类型的呼叫(表 profile_types)

  4. 用户可能有特殊权限,除了能够查看他们的配置文件中分配的区域外,还可以查看他们打开的任何呼叫(表配置文件上的布尔列)

我发布了一个精简的、列重命名的主要结构版本。我不认为我将能够发布真正的表格,它跨越了 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 相加,但许多其他因素使得这非常麻烦。

理想情况下,我们试图阻止我们的客户使用如此广泛的配置文件权限,但趋势似乎是对配置文件类型的更新和更模糊的需求(这就是实施“查看他打开的所有呼叫”的原因,因为例子)。

我们应该遵循什么更好的策略?

4

1 回答 1

1

这似乎太长了,无法发表评论。我的猜测是“或”正在扼杀当前索引结构的性能。

一种可能性是将查询分解为联合语句:

SELECT CALLID FROM CALLS
WHERE CALLS.USERID_OPENED_BY = 1 
union
SELECT CALLID FROM CALLS
WHERE CALLS.AREAID IN (1,2,3)
union 
SELECT CALLID FROM CALLS
WHERE CALLS.TYPEID IN (1,2,3) 

通常,我讨厌这种变化,但你可以看看它是否改进了执行计划。请注意,我在这里使用“联合”而不是“全部联合”来消除重复项。

但是,你说这是不可能的。

另一种想法是将区域和类型组合到同一个参考表中。这会将 WHERE 子句中的索引数量减少到两个,从而可能使连接更加优化。否则,您能否训练您的用户不要同时选择所有三个?应用程序真的需要这个功能吗?

于 2012-05-31T20:20:06.073 回答