sql >> データベース >  >> RDS >> Mysql

JOINedの同等のものよりもパフォーマンスが高い派生テーブルクエリをさらに最適化するにはどうすればよいですか?

    さて、私は解決策を見つけました。多くの実験が必要で、少し運が悪かったと思いますが、ここにあります:

    CREATE TABLE magic ENGINE=MEMORY
    SELECT
      s.shop_id AS shop_id,
      s.id AS shift_id,
      st.dow AS dow,
      st.start AS start,
      st.end AS end,
      su.user_id AS manager_id
    FROM shifts s
    JOIN shift_times st ON s.id = st.shift_id
    JOIN shifts_users su ON s.id = su.shift_id
    JOIN shift_positions sp ON su.shift_position_id = sp.id AND sp.level = 1
    
    ALTER TABLE magic ADD INDEX (shop_id, dow);
    
    CREATE TABLE tickets_extra ENGINE=MyISAM
    SELECT 
      t.id AS ticket_id,
      (
        SELECT m.manager_id
        FROM magic m
        WHERE DAYOFWEEK(t.created) = m.dow
        AND TIME(t.created) BETWEEN m.start AND m.end
        AND m.shop_id = t.shop_id
      ) AS manager_created,
      (
        SELECT m.manager_id
        FROM magic m
        WHERE DAYOFWEEK(t.resolved) = m.dow
        AND TIME(t.resolved) BETWEEN m.start AND m.end
        AND m.shop_id = t.shop_id
      ) AS manager_resolved
    FROM tickets t;
    DROP TABLE magic;
    

    長い説明

    ここで、これが機能する理由と、ここに到達するためのプロセスと手順を説明します。

    最初に、私が試みていたクエリが巨大な派生テーブルのために苦しんでいることを知っていました、そしてそれに続くJOINはこれに。インデックスが適切なチケットテーブルを取得し、すべてのshift_timesデータをそのテーブルに結合してから、MySQLがshiftsおよびshift_positionsテーブルを結合しようとしている間、それを噛み砕きました。この派生した巨大なものは、最大200万行のインデックス付けされていない混乱になります。

    今、私はこれが起こっていることを知っていました。しかし、私がこの道を進んだ理由は、厳密にJOINを使用してこれを行う「適切な」方法には、さらに長い時間がかかっていたためです。これは、特定のシフトのマネージャーが誰であるかを判断するために必要な厄介な混乱によるものです。正しいシフトが何であるかを知るためにshift_timesに参加すると同時に、ユーザーのレベルを把握するためにshift_positionsに参加する必要があります。 MySQLオプティマイザはこれをうまく処理できないと思います。最終的には、結合の一時テーブルの巨大な怪物を作成し、適用されないものを除外します。

    それで、派生したテーブルが「進むべき道」であるように思われたので、私はしばらくこれに頑固に固執しました。私はそれをJOIN句にパントしてみましたが、改善はありませんでした。派生テーブルを含む一時テーブルを作成しようとしましたが、一時テーブルのインデックスが作成されていないため、速度が遅すぎました。

    このシフト、時間、位置の計算を適切に処理する必要があることに気づきました。多分VIEWが行く方法だろうと思いました。この情報を含むVIEWを作成した場合はどうなりますか:(shop_id、shift_id、dow、start、end、manager_id)。次に、shop_idとDAYOFWEEK / TIMEの計算全体でチケットテーブルに参加するだけで、ビジネスを開始できます。もちろん、MySQLがVIEWをかなり慎重に処理することを思い出せませんでした。それらはまったく具体化されません。ビューを取得するために使用したクエリを実行するだけです。したがって、これにチケットを参加させることで、基本的に元のクエリを実行していましたが、改善はありませんでした。

    そこで、VIEWの代わりにTEMPORARYTABLEを使用することにしました。これは、一度に1つのマネージャー(作成または解決済み)のみをフェッチした場合はうまく機能しましたが、それでもかなり低速でした。また、MySQLでは、同じクエリで同じテーブルを2回参照できないことがわかりました(manager_createdとmanager_resolvedを区別できるようにするには、一時テーブルを2回結合する必要があります)。これは大きなWTFであり、「TEMPORARY」を指定しない限り実行できます。ここで、CREATETABLEマジックENGINE=MEMORYが機能します。

    この疑似一時テーブルを使用して、manager_createdだけでJOINを再試行しました。それはうまく機能しましたが、それでもかなり遅いです。それでも、同じクエリでmanager_resolvedを取得するために再度参加したとき、クエリ時間は成層圏に戻ってきました。 EXPLAINを見ると、予想どおり、チケットの全表スキャン(行〜2mln)が示され、それぞれ〜2,087でマジックテーブルに参加しました。繰り返しになりますが、私は失敗に直面しているようでした。

    私は今、JOINを完全に回避する方法について考え始めました。そのとき、誰かが副選択を使用することを提案した、あいまいな古代の掲示板の投稿を見つけました(私の履歴にリンクが見つかりません)。これが、上記の2番目のSELECTクエリ(tickets_extra作成クエリ)につながったものです。単一のマネージャーフィールドのみを選択した場合、それはうまく機能しましたが、両方ともそれはがらくたでした。 EXPLAINを見て、これを見ました:

    *************************** 1. row ***************************
               id: 1
      select_type: PRIMARY
            table: t
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 173825
            Extra: 
    *************************** 2. row ***************************
               id: 3
      select_type: DEPENDENT SUBQUERY
            table: m
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 2037
            Extra: Using where
    *************************** 3. row ***************************
               id: 2
      select_type: DEPENDENT SUBQUERY
            table: m
             type: ALL
    possible_keys: NULL
              key: NULL
          key_len: NULL
              ref: NULL
             rows: 2037
            Extra: Using where
    3 rows in set (0.00 sec)
    

    ああ、恐ろしい依存サブクエリ。 MySQLは通常、これらを外部から実行し、外部のすべての行に対して内部クエリを実行するため、これらを回避することをお勧めします。私はこれを無視して、「まあ...このばかげた魔法のテーブルにインデックスを付けたらどうなるだろうか」と疑問に思いました。このようにして、ADDインデックス(shop_id、dow)が生まれました。

    これをチェックしてください:

    mysql> CREATE TABLE magic ENGINE=MEMORY
    <snip>
    Query OK, 3220 rows affected (0.40 sec)
    
    mysql> ALTER TABLE magic ADD INDEX (shop_id, dow);
    Query OK, 3220 rows affected (0.02 sec)
    
    mysql> CREATE TABLE tickets_extra ENGINE=MyISAM
    <snip>
    Query OK, 1933769 rows affected (24.18 sec)
    
    mysql> drop table magic;
    Query OK, 0 rows affected (0.00 sec)
    

    それは 私が話していること!

    結論

    単一のクエリを効率的に実行するために、非TEMPORARYテーブルをその場で作成し、その場でインデックスを作成するのは、これが初めてです。その場でインデックスを追加することは、法外に費用のかかる操作だといつも思っていたと思います。 (2mln行のチケットテーブルにインデックスを追加すると、1時間以上かかる場合があります)。それでも、たった3,000行の場合、これは簡単なことです。

    DEPENDENT SUBQUERIESを恐れないでください。実際にはそうではない一時的なテーブルを作成したり、その場でインデックスを作成したり、エイリアンを作成したりします。これらはすべて、適切な状況で良いものになる可能性があります。

    StackOverflowのすべてのヘルプに感謝します。 :-D



    1. オブジェクト'xxxxxxx'、データベース'zzzzzzz'、スキーマ'dbo'に対するEXECUTE権限が拒否されました

    2. 注釈を使用してDoctrine2にFULLTEXTインデックスを追加しますか?

    3. CentOS7でMySQL8.0を使用してPHP5アプリケーションを実行する方法

    4. 私のSQLダイナミッククエリが実行され、ストアドプロシージャの変数に出力されます