使用嵌套的where子句优化查询

时间:2012-05-31 19:22:54

标签: sql nested firebird where

我使用这种布局(Firebird 2.1)处理呼叫/服务台软件:

  1. 通过从该区域选择区域和呼叫类型(表格呼叫,区域和类型)来打开呼叫

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

  3. 根据个人资料,用户可能能够查看或编辑某个区域的所有类型(table profile_areas),或者 只需查看或编辑所选的通话类型(表profile_types)

  4. 除了能够查看其个人资料中指定的区域外,用户还可以获得特殊许可, 也可以查看他们打开的任何调用(表格配置文件上的布尔列)

  5. 我发布了主要结构的精简列重命名版本。我不认为我能够 发布真实的表格,其中包括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 */
    );
    

    我们开始看到我们的第一批客户达到了1000多万个电话标记,而且任何主要的简单查询都开始变得非常慢。

    在分析查询计划时,所有似乎都已正确编入索引,但索引读取的数量几乎总是显示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,但是其他一些因素使得这非常麻烦。

    理想情况下,我们试图限制我们的客户使用如此广泛的配置文件权限,但相反,趋势似乎是更新,更隐蔽的需求 分析的类型(这就是“看到他打开的所有电话”的原因,例如)。

    我们应该遵循哪种更好的策略?

1 个答案:

答案 0 :(得分:1)

评论这似乎太长了。我的猜测是“或”正在用当前的索引结构来破坏性能。

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

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子句中将索引数减少为两个,这可能会使连接更加优化。否则,您是否可以训练您的用户不要同时选择所有三个用户?这个功能真的需要应用程序吗?