次のクエリがあります。
SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(IF(transactions.status = 'COMPLETED', 1, 0)) AS sales
FROM analytics
LEFT JOIN transactions ON analytics.id = transactions.analytics
WHERE analytics.user_id = 52094
GROUP BY analytics.source
ORDER BY frequency DESC
LIMIT 10
分析テーブルには60Mの行があり、トランザクションテーブルには3Mの行があります。
このクエリでEXPLAIN
を実行すると、次のようになります。
+------+--------------+-----------------+--------+---------------------+-------------------+----------------------+---------------------------+----------+-----------+-------------------------------------------------+
| # id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | |
+------+--------------+-----------------+--------+---------------------+-------------------+----------------------+---------------------------+----------+-----------+-------------------------------------------------+
| '1' | 'SIMPLE' | 'analytics' | 'ref' | 'analytics_user_id | analytics_source' | 'analytics_user_id' | '5' | 'const' | '337662' | 'Using where; Using temporary; Using filesort' |
| '1' | 'SIMPLE' | 'transactions' | 'ref' | 'tran_analytics' | 'tran_analytics' | '5' | 'dijishop2.analytics.id' | '1' | NULL | |
+------+--------------+-----------------+--------+---------------------+-------------------+----------------------+---------------------------+----------+-----------+-------------------------------------------------+
このクエリは既に非常に基本的なものであるため、このクエリを最適化する方法を理解できません。このクエリの実行には約70秒かかります。
存在するインデックスは次のとおりです。
+-------------+-------------+----------------------------+---------------+------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
| # Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------------+-------------+----------------------------+---------------+------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
| 'analytics' | '0' | 'PRIMARY' | '1' | 'id' | 'A' | '56934235' | NULL | NULL | '' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_user_id' | '1' | 'user_id' | 'A' | '130583' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_product_id' | '1' | 'product_id' | 'A' | '490812' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_affil_user_id' | '1' | 'affil_user_id' | 'A' | '55222' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_source' | '1' | 'source' | 'A' | '24604' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_country_name' | '1' | 'country_name' | 'A' | '39510' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_gordon' | '1' | 'id' | 'A' | '56934235' | NULL | NULL | '' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_gordon' | '2' | 'user_id' | 'A' | '56934235' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'analytics' | '1' | 'analytics_gordon' | '3' | 'source' | 'A' | '56934235' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
+-------------+-------------+----------------------------+---------------+------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
+----------------+-------------+-------------------+---------------+-------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
| # Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------------+-------------+-------------------+---------------+-------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
| 'transactions' | '0' | 'PRIMARY' | '1' | 'id' | 'A' | '2436151' | NULL | NULL | '' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'tran_user_id' | '1' | 'user_id' | 'A' | '56654' | NULL | NULL | '' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'transaction_id' | '1' | 'transaction_id' | 'A' | '2436151' | '191' | NULL | 'YES' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'tran_analytics' | '1' | 'analytics' | 'A' | '2436151' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'tran_status' | '1' | 'status' | 'A' | '22' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'gordon_trans' | '1' | 'status' | 'A' | '22' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
| 'transactions' | '1' | 'gordon_trans' | '2' | 'analytics' | 'A' | '2436151' | NULL | NULL | 'YES' | 'BTREE' | '' | '' |
+----------------+-------------+-------------------+---------------+-------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
状況を改善しなかったため、提案されたように追加のインデックスを追加する前に2つのテーブルのスキーマを簡略化しました。
CREATE TABLE `analytics` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`affil_user_id` int(11) DEFAULT NULL,
`product_id` int(11) DEFAULT NULL,
`medium` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`source` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`terms` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`is_browser` tinyint(1) DEFAULT NULL,
`is_mobile` tinyint(1) DEFAULT NULL,
`is_robot` tinyint(1) DEFAULT NULL,
`browser` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`mobile` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`robot` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`platform` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`referrer` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`domain` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`ip` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`continent_code` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`country_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`city` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `analytics_user_id` (`user_id`),
KEY `analytics_product_id` (`product_id`),
KEY `analytics_affil_user_id` (`affil_user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=64821325 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `transactions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`transaction_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`user_id` int(11) NOT NULL,
`pay_key` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`sender_email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`amount` decimal(10,2) DEFAULT NULL,
`currency` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`status` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`analytics` int(11) DEFAULT NULL,
`ip_address` varchar(46) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`session_id` varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`eu_vat_applied` int(1) DEFAULT '0',
PRIMARY KEY (`id`),
KEY `tran_user_id` (`user_id`),
KEY `transaction_id` (`transaction_id`(191)),
KEY `tran_analytics` (`analytics`),
KEY `tran_status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=10019356 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
上記の場合、これ以上最適化できません。サマリーテーブルに関する実装のアドバイスはすばらしいでしょう。 AWSではLAMPスタックを使用しています。上記のクエリはRDS(m1.large)で実行されています。
次のインデックス(bツリーインデックス)を作成します。
analytics(user_id, source, id)
transactions(analytics, status)
これはゴードンの提案とは異なります。
インデックス内の列の順序は重要です。
特定のanalytics.user_id
でフィルタリングするため、このフィールドはインデックスの最初にする必要があります。次に、analytics.source
でグループ化します。 source
によるソートを回避するには、これをインデックスの次のフィールドにする必要があります。 analytics.id
も参照するため、このフィールドをインデックスの一部として使用し、最後に置くことをお勧めします。 MySQLはインデックスのみを読み取ることができ、テーブルを操作することはできませんか?わかりませんが、テストはかなり簡単です。
transactions
で使用されるため、analytics
のインデックスはJOIN
で始まる必要があります。 status
も必要です。
SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(IF(transactions.status = 'COMPLETED', 1, 0)) AS sales
FROM analytics
LEFT JOIN transactions ON analytics.id = transactions.analytics
WHERE analytics.user_id = 52094
GROUP BY analytics.source
ORDER BY frequency DESC
LIMIT 10
最初にいくつかの分析...
_SELECT a.source AS referrer,
COUNT(*) AS frequency, -- See question below
SUM(t.status = 'COMPLETED') AS sales
FROM analytics AS a
LEFT JOIN transactions AS t ON a.id = t.analytics AS a
WHERE a.user_id = 52094
GROUP BY a.source
ORDER BY frequency DESC
LIMIT 10
_
a
からt
へのマッピングが「1対多」である場合、COUNT
とSUM
が正しい値であるかどうかを検討する必要がありますまたは膨らんだ値。クエリの現状では、これらは「膨張」しています。 JOIN
は集計の前に発生するため、トランザクションの数と完了したトランザクションの数を数えています。それが望ましいと思います。
注:通常のパターンはCOUNT(*)
;です。 COUNT(x)
と言うことは、x
がNULL
であることを確認することを意味します。チェックは必要ないと思いますか?
このインデックスはWHERE
を処理し、「カバー」しています。
_ analytics: INDEX(user_id, source, id) -- user_id first
transactions: INDEX(analytics, status) -- in this order
_
_GROUP BY
_は「ソート」を必要とする場合と必要としない場合があります。 _ORDER BY
_は_GROUP BY
_とは異なり、間違いなくソートが必要になります。そして、グループ化された行のセット全体をソートする必要があります。 LIMIT
へのショートカットはありません。
通常、サマリー表は日付指向です。つまり、_PRIMARY KEY
_には「日付」とその他のディメンションが含まれます。おそらく、日付とuser_idによるキー入力は理にかなっていますか?平均的なユーザーの1日あたりのトランザクション数はいくつですか? 10以上の場合は、サマリーテーブルを検討してみましょう。また、UPDATEing
またはDELETEing
の古いレコードにならないことが重要です。 もっと
たぶん
_user_id ...,
source ...,
dy DATE ...,
status ...,
freq MEDIUMINT UNSIGNED NOT NULL,
status_ct MEDIUMINT UNSIGNED NOT NULL,
PRIMARY KEY(user_id, status, source, dy)
_
次に、クエリは
_SELECT source AS referrer,
SUM(freq) AS frequency,
SUM(status_ct) AS completed_sales
FROM Summary
WHERE user_id = 52094
AND status = 'COMPLETED'
GROUP BY source
ORDER BY frequency DESC
LIMIT 10
_
速度は多くの要因からきています
JOIN
(まだ追加のソートが必要です。)
要約表がなくても、速度が向上する可能性があります...
Normalizing
一部の文字列がかさばって反復的であると、そのテーブルがI/Oバインドされなくなる可能性があります。KEY (transaction_id(191))
;それを修正する5つの方法については、 ここ を参照してください。utf8mb4_unicode_ci
_も必要ありません。 (39)とasciiで十分です。このクエリの場合:
_SELECT a.source AS referrer,
COUNT(*) AS frequency,
SUM( t.status = 'COMPLETED' ) AS sales
FROM analytics a LEFT JOIN
transactions t
ON a.id = t.analytics
WHERE a.user_id = 52094
GROUP BY a.source
ORDER BY frequency DESC
LIMIT 10 ;
_
analytics(user_id, id, source)
およびtransactions(analytics, status)
のインデックスが必要です。
以下をお試しいただき、問題が解決するかどうかをお知らせください。
SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(IF(transactions.status = 'COMPLETED', 1, 0)) AS sales
FROM (SELECT * FROM analytics where user_id = 52094) analytics
LEFT JOIN (SELECT analytics, status from transactions where analytics = 52094) transactions ON analytics.id = transactions.analytics
GROUP BY analytics.source
ORDER BY frequency DESC
LIMIT 10
このクエリは、数百万のanalytics
レコードをtransactions
レコードと結合する可能性があり、数百万のレコードの合計(ステータスチェックを含む)を計算します。最初にLIMIT 10
を適用してから結合を行い、合計を計算できれば、クエリを高速化できます。残念ながら、結合にはanalytics.id
が必要です。これはGROUP BY
を適用すると失われます。しかし、多分analytics.source
はとにかくクエリをブーストするのに十分な選択性があります。
したがって、私の考えは、頻度を計算し、それらによって制限し、サブクエリでanalytics.source
とfrequency
を返し、この結果を使用してメインクエリでanalytics
をフィルタリングすることです。これにより、残りの結合と計算がレコードの数が減ると期待されます。
最小限のサブクエリ(注:結合なし、合計なし、10レコードを返します):
SELECT
source,
COUNT(id) AS frequency
FROM analytics
WHERE user_id = 52094
GROUP BY source
ORDER BY frequency DESC
LIMIT 10
上記のクエリをサブクエリx
として使用した完全なクエリ:
SELECT
x.source AS referrer,
x.frequency,
SUM(IF(t.status = 'COMPLETED', 1, 0)) AS sales
FROM
(<subquery here>) x
INNER JOIN analytics a
ON x.source = a.source -- This reduces the number of records
LEFT JOIN transactions t
ON a.id = t.analytics
WHERE a.user_id = 52094 -- We could have several users per source
GROUP BY x.source, x.frequency
ORDER BY x.frequency DESC
これによって期待されるパフォーマンスが向上しない場合は、MySQLが予期しない順序で結合を適用していることが原因である可能性があります。ここで説明したように "MySQLの実行順序を強制する方法はありますか?" この場合、結合をSTRAIGHT_JOIN
で置き換えることができます。
以下のアプローチを試していただけませんか:
SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(sales) AS sales
FROM analytics
LEFT JOIN(
SELECT transactions.Analytics, (CASE WHEN transactions.status = 'COMPLETED' THEN 1 ELSE 0 END) AS sales
FROM analytics INNER JOIN transactions ON analytics.id = transactions.analytics
) Tra
ON analytics.id = Tra.analytics
WHERE analytics.user_id = 52094
GROUP BY analytics.source
ORDER BY frequency DESC
LIMIT 10
これを試して
SELECT
a.source AS referrer,
COUNT(a.id) AS frequency,
SUM(t.sales) AS sales
FROM (Select id, source From analytics Where user_id = 52094) a
LEFT JOIN (Select analytics, case when status = 'COMPLETED' Then 1 else 0 end as sales
From transactions) t ON a.id = t.analytics
GROUP BY a.source
ORDER BY frequency DESC
LIMIT 10
「大規模なテーブルです」とおっしゃっていたので、これを提案しますが、このSQLはごく少数の列のみを使用します。この場合、必要な列のみを含むインラインビューを使用すると、
注:ここでもメモリが重要な役割を果たします。インラインビューを決定する前に、メモリを確認してください
2つのテーブルからクエリを分離しようと思います。上位10個のsource
sだけが必要なので、最初にそれらを取得してから、transactions
sales
列からクエリを実行します。
SELECT source as referrer
,frequency
,(select count(*)
from transactions t
where t.analytics in (select distinct id
from analytics
where user_id = 52094
and source = by_frequency.source)
and status = 'completed'
) as sales
from (SELECT analytics.source
,count(*) as frequency
from analytics
where analytics.user_id = 52094
group by analytics.source
order by frequency desc
limit 10
) by_frequency
distinct
がない場合も高速になる可能性があります
述語user_id = 52094は説明のためであり、アプリケーションでは、選択されたuser_idは変数であると想定しています。
ここでは、ACIDプロパティはそれほど重要ではないと思います。
(1)したがって、ユーティリティテーブルを使用して、必要なフィールドのみを持つ2つのレプリカテーブル(Vladimirが上で提案したインデックスと同様)を維持します。
CREATE TABLE mv_anal (
`id` int(11) NOT NULL,
`user_id` int(11) DEFAULT NULL,
`source` varchar(45),
PRIMARY KEY (`id`)
);
CREATE TABLE mv_trans (
`id` int(11) NOT NULL,
`status` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`analytics` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE util (
last_updated_anal int (11) NOT NULL,
last_updated_trans int (11) NOT NULL
);
INSERT INTO util (0, 0);
ここでの利点は、元のテーブルの比較的小さな予測を読み取ることです。うまくいけば、OSレベルとDBレベルのキャッシュは機能し、低速のセカンダリストレージからではなく高速のRAMから読み取られます。 これは非常に大きな利益になる可能性があります。
これが2つのテーブルを更新する方法です(以下はcronによって実行されるトランザクションです)。
-- TRANSACTION STARTS --
INSERT INTO mv_trans
SELECT id, IF (status = 'COMPLETE', 1, 0) AS status, analysis
FROM transactions JOIN util
ON util.last_updated_trans <= transactions.id
UPDATE util
SET last_updated_trans = sub.m
FROM (SELECT MAX (id) AS m FROM mv_trans) sub;
-- TRANSACTION COMMITS --
-- similar transaction for mv_anal.
(2)次に、選択性に取り組み、順次スキャン時間を短縮します。 mv_analのuser_id、source、id(このシーケンス)にbツリーインデックスを作成する必要があります。
注:上記は、アナリティクステーブルにインデックスを作成するだけで実現できますが、そのようなインデックスを作成するには、60M行の大きなテーブルを読み取る必要があります。私の方法では、非常に薄いテーブルのみを読み取るためにインデックスの構築が必要です。したがって、btreeをより頻繁に再構築できます(テーブルが追加専用であるため、スキューの問題に対処します)。
これは、私が確認する方法ですクエリ時に高い選択性が達成されますで、btreeの問題を歪めます。
(3)PostgreSQLでは、WITHサブクエリは常にマテリアライズされます。 MySQLについても同様に願っています。したがって、最適化の最後のマイルとして:
WITH sub_anal AS (
SELECT user_id, source AS referrer, COUNT (id) AS frequency
FROM mv_anal
WHERE user_id = 52094
GROUP BY user_id, source
ORDER BY COUNT (id) DESC
LIMIT 10
)
SELECT sa.referrer, sa.frequency, SUM (status) AS sales
FROM sub_anal AS sa
JOIN mv_anal anal
ON sa.referrer = anal.source AND sa.user_id = anal.user_id
JOIN mv_trans AS trans
ON anal.id = trans.analytics
私はサブクエリを試してみます:
SELECT a.source AS referrer,
COUNT(*) AS frequency,
SUM((SELECT COUNT(*) FROM transactions t
WHERE a.id = t.analytics AND t.status = 'COMPLETED')) AS sales
FROM analytics a
WHERE a.user_id = 52094
GROUP BY a.source
ORDER BY frequency DESC
LIMIT 10;
さらに、@ Gordonの回答とまったく同じようにインデックスを作成します:analytics(user_id、id、source)およびトランザクション(analytics、status)。
あなたのクエリで私が見つける唯一の問題は
GROUP BY analytics.source
ORDER BY frequency DESC
このクエリのため、一時テーブルを使用してファイルソートを行っています。
これを回避する1つの方法は、次のような別のテーブルを作成することです。
CREATE TABLE `analytics_aggr` (
`source` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`frequency` int(10) DEFAULT NULL,
`sales` int(10) DEFAULT NULL,
KEY `sales` (`sales`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`
以下のクエリを使用してanalytics_aggrにデータを挿入します
insert into analytics_aggr SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(IF(transactions.status = 'COMPLETED', 1, 0)) AS sales
FROM analytics
LEFT JOIN transactions ON analytics.id = transactions.analytics
WHERE analytics.user_id = 52094
GROUP BY analytics.source
ORDER BY null
これで、簡単にデータを取得できます
select * from analytics_aggr order by sales desc
パーティーに遅れました。 MySQLのキャッシュに1つのインデックスをロードする必要があると思います。 NLJはおそらくパフォーマンスを低下させています。ここに私がそれを見る方法があります:
パス
クエリは簡単です。 2つのテーブルがあり、「パス」は非常に明確です。
analytics
テーブルを読み取ることを計画する必要があります。transactions
テーブルを2番目に読み取ることを計画する必要があります。これは、LEFT OUTER JOIN
を使用しているためです。これについてはあまり議論しません。analytics
テーブルは6,000万行であり、最適なパスはこの行で行をできるだけ早くフィルタリングする必要があります。アクセス
パスが明確になったら、インデックスアクセスとテーブルアクセスのどちらを使用するかを決定する必要があります。どちらにも長所と短所があります。ただし、SELECT
のパフォーマンスを向上させる必要があります。
フィルタリング
繰り返しますが、SELECT
の高いパフォーマンスが必要です。したがって:
行の集計
フィルタリング後、次のステップはGROUP BY analytics.source
で行を集計することです。これは、source
列をインデックスの最初の列として配置することで改善できます。
パス、アクセス、フィルタリング、および集約に最適なインデックス
上記のすべてを考慮して、言及されたすべての列をインデックスに含める必要があります。次のインデックスは、応答時間を改善するはずです。
create index ix1_analytics on analytics (user_id, source, id);
create index ix2_transactions on transactions (analytics, status);
これらのインデックスは、上記の「パス」、「アクセス」、および「フィルタリング」戦略を満たします。
インデックスキャッシュ
最後に-これは重要です-セカンダリインデックスをMySQLのメモリキャッシュにロードします。 MySQLはNLJ(Nested Loop Join)(MySQL用語では「ref」)を実行しており、2番目のアクセスにはランダムに約200k回アクセスする必要があります。
残念ながら、MySQLのキャッシュにインデックスをロードする方法がわかりません。 FORCE
の使用は、次のように機能します。
SELECT
analytics.source AS referrer,
COUNT(analytics.id) AS frequency,
SUM(IF(transactions.status = 'COMPLETED', 1, 0)) AS sales
FROM analytics
LEFT JOIN transactions FORCE index (ix2_transactions)
ON analytics.id = transactions.analytics
WHERE analytics.user_id = 52094
GROUP BY analytics.source
ORDER BY frequency DESC
LIMIT 10
十分なキャッシュ領域があることを確認してください。これを理解するための短い質問/答えは次のとおりです: mysqlインデックスが完全にメモリに収まるかどうかを理解する方法
幸運を!ああ、結果を投稿してください。
この質問は間違いなく多くの注目を集めているので、すべての明白な解決策が試されたと私は確信しています。ただし、クエリでLEFT JOIN
に対応するものは見つかりませんでした。
LEFT JOIN
ステートメントは通常、クエリプランナーにハッシュ結合を強制します。ハッシュ結合は、少数の結果では高速ですが、多数の結果では非常に遅くなります。 @Rick Jamesの回答で述べたように、元のクエリの結合はIDフィールドanalytics.id
にあるため、これは多数の結果を生成します。ハッシュ結合はひどいパフォーマンス結果をもたらします。以下の提案は、スキーマや処理を変更することなく、これに対処します。
集計はanalytics.source
によって行われるため、ソース別の頻度とソース別の売上に個別の集計を作成し、集計が完了するまで左結合を延期するクエリを試します。これにより、インデックスを最適に使用できます(通常、これは大きなデータセットのマージ結合です)。
これが私の提案です:
SELECT t1.source AS referrer, t1.frequency, t2.sales
FROM (
-- Frequency by source
SELECT a.source, COUNT(a.id) AS frequency
FROM analytics a
WHERE a.user_id=52094
GROUP BY a.source
) t1
LEFT JOIN (
-- Sales by source
SELECT a.source,
SUM(IF(t.status = 'COMPLETED', 1, 0)) AS sales
FROM analytics a
JOIN transactions t
WHERE a.id = t.analytics
AND t.status = 'COMPLETED'
AND a.user_id=52094
GROUP by a.source
) t2
ON t1.source = t2.source
ORDER BY frequency DESC
LIMIT 10
お役に立てれば。