如何优化此 MySQL 查询?数百万行
How to optimise this MySQL query? Millions of Rows
我有以下查询:
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
分析 table 有 6000 万行,交易 table 有 300 万行。
当我在此查询中 运行 一个 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' | '' | '' |
+----------------+-------------+-------------------+---------------+-------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
在按照建议添加任何额外索引之前简化两个 table 的架构,因为它没有改善情况。
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;
如果以上无法进一步优化。任何关于摘要 table 的实施建议都会很棒。我们在 AWS 上使用 LAMP 堆栈。上面的查询是 运行ning on RDS (m1.large).
对于此查询:
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
我会尝试子查询:
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;
Plus 索引与@Gordon 的回答完全相同:analytics(user_id, id, source) and transactions(analytics, status)。
我将创建以下索引(b 树索引):
analytics(user_id, source, id)
transactions(analytics, status)
这与戈登的建议不同。
索引中列的顺序很重要。
您按特定 analytics.user_id
筛选,因此该字段必须位于索引中的第一个。
然后按 analytics.source
分组。为避免按 source
排序,这应该是索引的下一个字段。您还引用了analytics.id
,所以最好将此字段作为索引的一部分,放在最后。 MySQL 是否能够只读取索引而不触及 table?我不知道,但它很容易测试。
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
的映射是"one-to-many",那么你需要考虑COUNT
和SUM
是否有正确的值或者夸大的价值观。就查询而言,它们是 "inflated"。 JOIN
发生在 聚合之前 ,因此您计算的是交易数量和已完成的交易数量。我假设这是需要的。
注意:通常的模式是COUNT(*)
;说 COUNT(x)
意味着检查 x
是否为 NULL
。我怀疑不需要检查?
该索引处理 WHERE
并且是 "covering":
analytics: INDEX(user_id, source, id) -- user_id first
transactions: INDEX(analytics, status) -- in this order
GROUP BY
可能需要也可能不需要 'sort'。 ORDER BY
与 GROUP BY
不同,肯定需要排序。并且需要对整个分组的行集进行排序; LIMIT
.
没有捷径
通常,摘要 table 是面向日期的。也就是说,PRIMARY KEY
包括一个 'date' 和一些其他维度。也许,按日期和 user_id 键入有意义吗?一般用户每天有多少笔交易?如果至少有 10 个,那么让我们考虑一个摘要 table。此外,重要的是不要成为 UPDATEing
或 DELETEing
旧记录。 More
我可能会
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
速度来自很多因素
- 更小 table(要查看的行数更少)
- 没有
JOIN
- 更有用的索引
(还需要额外的排序。)
即使没有摘要 table,也可能会有一些加速...
- table 有多大? `innodb_buffer_pool_size 有多大?
Normalizing
一些既庞大又重复的字符串可能会使 table 而不是 I/O-bound。
- 这太糟糕了:
KEY (transaction_id(191))
;请参阅 here 了解 5 种修复方法。
- IP 地址不需要 255 个字节,也
utf8mb4_unicode_ci
。 (39) 和 ascii 就足够了。
我在您的查询中发现的唯一问题是
GROUP BY analytics.source
ORDER BY frequency DESC
因为此查询正在使用临时 table.
进行文件排序
避免这种情况的一种方法是创建另一个 table 类似
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
你能试试下面的方法吗:
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
我提出这个建议是因为你说 "they are massive table" 但这个 sql 只使用了很少的列。在这种情况下,如果我们只使用带有 require 列的内联视图,那么它会很好
注意:内存在这里也将发挥重要作用。所以在决定内联视图之前先确认内存
我会尝试将查询与两个表分开。由于您只需要前 10 个 source
,我会先获取它们,然后从 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) 因此,我将使用实用程序 table 维护两个仅包含必要字段的副本 table(它类似于 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);
这里的收获是我们将读取原始 tables 的相对较小的投影 -- 希望 OS 级和数据库级缓存工作并且它们不会从较慢的位置读取辅助存储,但来自更快的 RAM。 这可以是一个很大的收获。
这是我更新两个 table 的方式(下面是 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) 现在,我将解决选择性问题以减少顺序扫描时间。我将不得不在 user_id 上构建一个 b 树索引,在 mv_anal 上构建源和 ID(按此顺序)。
注意:以上可以通过在分析 table 上创建索引来实现,但是构建这样的索引需要读取大 table 和 60M 行。我的方法要求索引构建只读取非常薄的 table。因此,我们可以更频繁地重建 btree(以解决倾斜问题,因为 table 是仅附加的)。
这就是我确保在查询 时实现高选择性并解决倾斜 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
聚会迟到了。我认为您需要将一个索引加载到 MySQL 的缓存中。 NLJ 可能会扼杀性能。这是我的看法:
路径
您的问题很简单。它有两个 table 并且 "path" 非常清楚:
- 优化器应计划首先读取
analytics
table。
- 优化器应该计划读取
transactions
table 秒。这是因为您使用的是 LEFT OUTER JOIN
。这个就不多讨论了。
- 此外,
analytics
table 是6000万行,最好的路径应该在这一行上尽快过滤行。
访问
路径清晰后,您需要决定是使用索引访问还是 Table 访问。两者各有利弊。但是,您想提高 SELECT
性能:
- 您应该选择索引访问。
- 避免混合访问。因此,您应该不惜一切代价避免任何 Table 访问(提取)。翻译:将所有参与的列放在索引中。
过滤
同样,您希望 SELECT
具有高性能。因此:
- 您应该在索引级别执行过滤,而不是在 table 级别。
行聚合
筛选后,下一步是按 GROUP BY analytics.source
聚合行。这可以通过将 source
列作为索引中的第一列来改进。
路径、访问、过滤和聚合的最佳索引
考虑到以上所有情况,您应该将所有提到的列都包含到索引中。以下指标应该会提高响应时间:
create index ix1_analytics on analytics (user_id, source, id);
create index ix2_transactions on transactions (analytics, status);
这些索引实现了上述 "path"、"access" 和 "filtering" 策略。
索引缓存
最后——这很关键——将二级索引加载到MySQL的内存缓存中。 MySQL 正在执行 NLJ(嵌套循环连接)——MySQL 术语中的 'ref'——并且需要随机访问第二个近 20 万次。
不幸的是,我不确定如何将索引加载到 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
确保你有足够的缓存 space。这是一个简短的 question/answer 来弄清楚:How to figure out if mysql index fits entirely in memory
祝你好运!哦,还有 post 结果。
此查询可能会将数百万条 analytics
条记录与 transactions
条记录连接起来,并计算数百万条记录的总和(包括状态检查)。
如果我们可以先应用 LIMIT 10
然后进行连接并计算总和,我们可以加快查询速度。
不幸的是,我们需要用于连接的 analytics.id
,它在应用 GROUP BY
后丢失了。但也许 analytics.source
的选择性足以提升查询。
因此,我的想法是计算子查询中 return 和 analytics.source
和 frequency
的频率,由它们限制,并使用此结果过滤 analytics
在主查询中,然后在希望减少的记录数量上进行其余的连接和计算。
最小子查询(注:无join,无sum,returns 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 以意外的顺序应用连接。如此处 "Is there a way to force MySQL execution order?" 所述,在这种情况下,您可以将连接替换为 STRAIGHT_JOIN
。
这个问题肯定受到了很多关注,所以我确信所有明显的解决方案都已尝试过。不过,我没有在查询中看到解决 LEFT JOIN
的内容。
我注意到 LEFT JOIN
语句通常会强制查询计划器进入散列连接,这对于少量结果来说速度很快,但对于大量结果来说非常慢。正如@Rick James 的回答中所述,由于原始查询中的连接是在身份字段 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
希望对您有所帮助。
我有以下查询:
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
分析 table 有 6000 万行,交易 table 有 300 万行。
当我在此查询中 运行 一个 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' | '' | '' |
+----------------+-------------+-------------------+---------------+-------------------+------------+--------------+-----------+---------+--------+-------------+----------+----------------+
在按照建议添加任何额外索引之前简化两个 table 的架构,因为它没有改善情况。
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;
如果以上无法进一步优化。任何关于摘要 table 的实施建议都会很棒。我们在 AWS 上使用 LAMP 堆栈。上面的查询是 运行ning on RDS (m1.large).
对于此查询:
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
我会尝试子查询:
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;
Plus 索引与@Gordon 的回答完全相同:analytics(user_id, id, source) and transactions(analytics, status)。
我将创建以下索引(b 树索引):
analytics(user_id, source, id)
transactions(analytics, status)
这与戈登的建议不同。
索引中列的顺序很重要。
您按特定 analytics.user_id
筛选,因此该字段必须位于索引中的第一个。
然后按 analytics.source
分组。为避免按 source
排序,这应该是索引的下一个字段。您还引用了analytics.id
,所以最好将此字段作为索引的一部分,放在最后。 MySQL 是否能够只读取索引而不触及 table?我不知道,但它很容易测试。
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
的映射是"one-to-many",那么你需要考虑COUNT
和SUM
是否有正确的值或者夸大的价值观。就查询而言,它们是 "inflated"。 JOIN
发生在 聚合之前 ,因此您计算的是交易数量和已完成的交易数量。我假设这是需要的。
注意:通常的模式是COUNT(*)
;说 COUNT(x)
意味着检查 x
是否为 NULL
。我怀疑不需要检查?
该索引处理 WHERE
并且是 "covering":
analytics: INDEX(user_id, source, id) -- user_id first
transactions: INDEX(analytics, status) -- in this order
GROUP BY
可能需要也可能不需要 'sort'。 ORDER BY
与 GROUP BY
不同,肯定需要排序。并且需要对整个分组的行集进行排序; LIMIT
.
通常,摘要 table 是面向日期的。也就是说,PRIMARY KEY
包括一个 'date' 和一些其他维度。也许,按日期和 user_id 键入有意义吗?一般用户每天有多少笔交易?如果至少有 10 个,那么让我们考虑一个摘要 table。此外,重要的是不要成为 UPDATEing
或 DELETEing
旧记录。 More
我可能会
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
速度来自很多因素
- 更小 table(要查看的行数更少)
- 没有
JOIN
- 更有用的索引
(还需要额外的排序。)
即使没有摘要 table,也可能会有一些加速...
- table 有多大? `innodb_buffer_pool_size 有多大?
Normalizing
一些既庞大又重复的字符串可能会使 table 而不是 I/O-bound。- 这太糟糕了:
KEY (transaction_id(191))
;请参阅 here 了解 5 种修复方法。 - IP 地址不需要 255 个字节,也
utf8mb4_unicode_ci
。 (39) 和 ascii 就足够了。
我在您的查询中发现的唯一问题是
GROUP BY analytics.source
ORDER BY frequency DESC
因为此查询正在使用临时 table.
进行文件排序避免这种情况的一种方法是创建另一个 table 类似
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
你能试试下面的方法吗:
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
我提出这个建议是因为你说 "they are massive table" 但这个 sql 只使用了很少的列。在这种情况下,如果我们只使用带有 require 列的内联视图,那么它会很好
注意:内存在这里也将发挥重要作用。所以在决定内联视图之前先确认内存
我会尝试将查询与两个表分开。由于您只需要前 10 个 source
,我会先获取它们,然后从 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) 因此,我将使用实用程序 table 维护两个仅包含必要字段的副本 table(它类似于 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);
这里的收获是我们将读取原始 tables 的相对较小的投影 -- 希望 OS 级和数据库级缓存工作并且它们不会从较慢的位置读取辅助存储,但来自更快的 RAM。 这可以是一个很大的收获。
这是我更新两个 table 的方式(下面是 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) 现在,我将解决选择性问题以减少顺序扫描时间。我将不得不在 user_id 上构建一个 b 树索引,在 mv_anal 上构建源和 ID(按此顺序)。
注意:以上可以通过在分析 table 上创建索引来实现,但是构建这样的索引需要读取大 table 和 60M 行。我的方法要求索引构建只读取非常薄的 table。因此,我们可以更频繁地重建 btree(以解决倾斜问题,因为 table 是仅附加的)。
这就是我确保在查询 时实现高选择性并解决倾斜 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
聚会迟到了。我认为您需要将一个索引加载到 MySQL 的缓存中。 NLJ 可能会扼杀性能。这是我的看法:
路径
您的问题很简单。它有两个 table 并且 "path" 非常清楚:
- 优化器应计划首先读取
analytics
table。 - 优化器应该计划读取
transactions
table 秒。这是因为您使用的是LEFT OUTER JOIN
。这个就不多讨论了。 - 此外,
analytics
table 是6000万行,最好的路径应该在这一行上尽快过滤行。
访问
路径清晰后,您需要决定是使用索引访问还是 Table 访问。两者各有利弊。但是,您想提高 SELECT
性能:
- 您应该选择索引访问。
- 避免混合访问。因此,您应该不惜一切代价避免任何 Table 访问(提取)。翻译:将所有参与的列放在索引中。
过滤
同样,您希望 SELECT
具有高性能。因此:
- 您应该在索引级别执行过滤,而不是在 table 级别。
行聚合
筛选后,下一步是按 GROUP BY analytics.source
聚合行。这可以通过将 source
列作为索引中的第一列来改进。
路径、访问、过滤和聚合的最佳索引
考虑到以上所有情况,您应该将所有提到的列都包含到索引中。以下指标应该会提高响应时间:
create index ix1_analytics on analytics (user_id, source, id);
create index ix2_transactions on transactions (analytics, status);
这些索引实现了上述 "path"、"access" 和 "filtering" 策略。
索引缓存
最后——这很关键——将二级索引加载到MySQL的内存缓存中。 MySQL 正在执行 NLJ(嵌套循环连接)——MySQL 术语中的 'ref'——并且需要随机访问第二个近 20 万次。
不幸的是,我不确定如何将索引加载到 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
确保你有足够的缓存 space。这是一个简短的 question/answer 来弄清楚:How to figure out if mysql index fits entirely in memory
祝你好运!哦,还有 post 结果。
此查询可能会将数百万条 analytics
条记录与 transactions
条记录连接起来,并计算数百万条记录的总和(包括状态检查)。
如果我们可以先应用 LIMIT 10
然后进行连接并计算总和,我们可以加快查询速度。
不幸的是,我们需要用于连接的 analytics.id
,它在应用 GROUP BY
后丢失了。但也许 analytics.source
的选择性足以提升查询。
因此,我的想法是计算子查询中 return 和 analytics.source
和 frequency
的频率,由它们限制,并使用此结果过滤 analytics
在主查询中,然后在希望减少的记录数量上进行其余的连接和计算。
最小子查询(注:无join,无sum,returns 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 以意外的顺序应用连接。如此处 "Is there a way to force MySQL execution order?" 所述,在这种情况下,您可以将连接替换为 STRAIGHT_JOIN
。
这个问题肯定受到了很多关注,所以我确信所有明显的解决方案都已尝试过。不过,我没有在查询中看到解决 LEFT JOIN
的内容。
我注意到 LEFT JOIN
语句通常会强制查询计划器进入散列连接,这对于少量结果来说速度很快,但对于大量结果来说非常慢。正如@Rick James 的回答中所述,由于原始查询中的连接是在身份字段 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
希望对您有所帮助。