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