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 */
);

最初のクライアントが 1,000 万回以上の呼び出しを達成し始めており、主要な単純なクエリのいずれかが非常に遅くなり始めています。

クエリ プランを分析すると、すべてが適切にインデックス付けされているように見えますが、インデックス付けされた読み取りの数は、クエリが合計 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 を別のクエリに分割し、和集合で合計するようにアドバイスした人もいますが、他の多くの要因がそれを非常に面倒にしています。

理想的には、クライアントがそのような幅広いプロファイル権限を使用することを制限しようとしますが、代わりに、傾向はより新しく、より曖昧なタイプのプロファイリングのニーズのようです (これが、「彼が開いたすべての通話を表示する」が実装された理由です。例)。

私たちが従うべきより良い戦略はありますか?

4

1 に答える 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) 

通常、私はこの種の変更を嫌いますが、実行計画が改善されるかどうかを確認できます。重複を排除するために、ここでは "union all" ではなく "union" を使用していることに注意してください。

しかし、あなたはこれは不可能だと言います。

もう 1 つのアイデアは、領域とタイプを同じ参照テーブルに結合することです。これにより、WHERE 句のインデックスの数が 2 つに減り、おそらく結合がより最適になります。それ以外の場合、3 つすべてを同時に選択しないようにユーザーをトレーニングできますか? この機能はアプリケーションに本当に必要ですか?

于 2012-05-31T20:20:06.073 に答える