2012-05-31 115 views
2

林在电话/的ServiceDesk软件的工作与这种布局(火鸟2.1):嵌套优化查询的WHERE子句

  1. 呼叫是通过选择一个区域,从该区域呼叫类型的打开(表呼叫,区域和类型)

  2. 用户分配轮廓它说哪些领域和类型,他们可以查看或编辑电话(表用户和表谱)

  3. 根据不同的配置文件,用户可能能够查看或编辑ALL类型一定面积的(表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+万次呼叫标记,以及任何主要的简单查询也开始变得很慢。

在对查询计划进行分析时,似乎所有索引都已正确编制索引,但即使查询 总共返回了5个左右的结果,索引读取的数量几乎总是显示1000万左右。

这个问题似乎是因为配置文件可能有很多不同的变体,我们并没有成功地使用连接来构建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分解为不同的查询并加入联合,但其他一些因素使其非常麻烦。

理想情况下,我们试图限制我们的客户使用如此大范围的配置文件权限变化,但相反,这种趋势似乎是更新,更晦涩的配置类型的需求(这就是为什么“查看所有打开的电话” “例如被执行)。

我们应该关注哪些更好的策略?

+0

我们在这里讨论了多少个电话? – bluevector

+0

不好意思问这样一个愚蠢的问题,但显然你把索引放在FK领域不是吗? – Sebas

+0

为什么用户需要查看他们打开的所有呼叫以及他们分配的区域/类型中的所有呼叫?他们发起的所有呼叫是否都包含在他们分配的区域和/或类型中? 他们不能在他们无权查看的区域/类型中发起呼叫。他们似乎会(可能只应该)要么查看他们所分配的区域/类型中的所有呼叫或所有呼叫。 – Sean

回答

1

对于评论来说,这似乎太长。我的猜测是,“or”正在用当前的索引结构来消灭性能。

一种可能性是打破查询到工会声明:

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) 

通常情况下,我厌恶这种变化的,但你可以看到,如果它提高了执行计划。注意我在这里使用了“union”而不是“union all”来消除重复。

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

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

+0

联合会完全解决问题。他们不能使用,因为涉及的工作量很大。可以说,系统的90%是基于对呼叫表的查询,并且每个查询都必须满足用户配置文件。 我们的实际问题是要解决系统中所有其他的查询问题,其中不少都有集合函数等。 但是,我开始觉得这是一个需要(即使可怕)的工作在这一点上,我们将不得不采取联盟战略,并重构大部分数据方法。 如果不需要功能,客户端请求就非常具体。 – RudiBR

+0

SQL Server优化器完全按照您的说法进行操作。它将OR转化为UNION。你在使用哪种数据库引擎? – bluevector

+0

@jonnyGold他正在使用Firebird 2.1,请参阅TS。 –