MySQL巨大的表JOIN使数据库崩溃

fed*_*qui 3 mysql sql optimization performance greatest-n-per-group

按照我最近的问题从最后一项中选择信息并加入总金额,我在生成表时遇到了一些内存问题

我有两张桌子sales1,sales2像这样:

id | 日期| 客户| 拍卖

使用此表定义:

CREATE TABLE sales (
    id int auto_increment primary key, 
    dates date,
    customer int,
    sale int
);
Run Code Online (Sandbox Code Playgroud)

sales1并且sales2具有相同的定义,但sales2sale=-1在各个领域.客户可以在任何一个,一个或两个表中.两个表都有大约300.000条记录和比此处所示更多的字段(大约50个字段).他们是InnoDB.

我想为每个客户选择:

  • 购买数量
  • 最后的购买价值
  • 购买总金额,当它具有正值时

我使用的查询是:

SELECT a.customer, count(a.sale), max_sale
FROM sales a
INNER JOIN (SELECT customer, sale max_sale 
        from sales x where dates = (select max(dates) 
                                    from sales y 
                                    where x.customer = y.customer
                                    and y.sale > 0
                                   )

       )b
ON a.customer = b.customer
GROUP BY a.customer, max_sale;
Run Code Online (Sandbox Code Playgroud)

问题是:

我必须得到结果,我需要进行某些计算,将日期分开:2012年的信息,2013年的信息,以及所有年份的信息.

每当我做一年时,存储所有信息大约需要2-3分钟.

但是当我尝试从这些年来收集信息时,数据库崩溃了,我得到的信息如下:

InternalError: (InternalError) (1205, u'Lock wait timeout exceeded; try restarting transaction')
Run Code Online (Sandbox Code Playgroud)

加入如此庞大的表似乎对数据库来说太过分了.当我explain查询时,几乎所有的时间百分比都来自creating tmp table.

我想在分开数据收集的季度.我们每三个月获得一次结果,然后加入并对其进行排序.但我想最终的连接和排序对于数据库来说太多了.

那么,只要我无法更改表结构,您会建议什么才能优化这些查询?

Bil*_*win 13

300k行不是一张巨大的表.我们经常看到3亿行表.

您的查询的最大问题是您正在使用相关子查询,因此它必须为外部查询中的每一行重新执行子查询.

通常情况下,您不需要在一个SQL语句中完成所有工作.将它分解为几个更简单的SQL语句有一些优点:

  • 更容易编码.
  • 更容易优化.
  • 更容易调试.
  • 更容易阅读.
  • 如果/何时必须实施新要求,则更易于维护.

购买数量

SELECT customer, COUNT(sale) AS number_of_purchases
FROM sales 
GROUP BY customer;
Run Code Online (Sandbox Code Playgroud)

销售(客户,销售)索引最适合此查询.

上次购买价值

这是经常出现的最大n组问题.

SELECT a.customer, a.sale as max_sale
FROM sales a
LEFT OUTER JOIN sales b
 ON a.customer=b.customer AND a.dates < b.dates
WHERE b.customer IS NULL;
Run Code Online (Sandbox Code Playgroud)

换句话说,尝试将行匹配ab具有相同客户和更大日期的假设行.如果找不到这样的行,那么a该客户必须拥有最大的日期.

销售指数(客户,日期,销售)最适合此查询.

如果您在最大的日期可能为客户进行多次销售,则此查询将为每个客户返回多个行.你需要找到另一个栏来打破平局.如果您使用自动增量主键,它适合作为平局,因为它保证是唯一的,并且它往往会按时间顺序增加.

SELECT a.customer, a.sale as max_sale
FROM sales a
LEFT OUTER JOIN sales b
 ON a.customer=b.customer AND (a.dates < b.dates OR a.dates = b.dates and a.id < b.id)
WHERE b.customer IS NULL;
Run Code Online (Sandbox Code Playgroud)

购买总额,当它具有正值时

SELECT customer, SUM(sale) AS total_purchases
FROM sales
WHERE sale > 0
GROUP BY customer;
Run Code Online (Sandbox Code Playgroud)

销售(客户,销售)索引最适合此查询.

您应该考虑使用NULL来表示缺少的销售值而不是-1.SUM()和COUNT()等聚合函数忽略NULL,因此您不必使用WHERE子句来排除sales <0的行.


回复:你的评论

我现在拥有的是一个包含字段年,季度,total_sale(关于对(年,季))和销售的表.我想收集的是有关特定时期的信息:本季度,季度,2011年...信息必须分配给顶级客户,销售额较大的客户等.是否有可能从客户那里获得最后的购买价值total_purchases大于5?

2012年第四季度的前五大客户

SELECT customer, SUM(sale) AS total_purchases
FROM sales
WHERE (year, quarter) = (2012, 4) AND sale > 0
GROUP BY customer
ORDER BY total_purchases DESC
LIMIT 5;
Run Code Online (Sandbox Code Playgroud)

我想根据实际数据对其进行测试,但我认为销售指数(年,季度,客户,销售)最适合此查询.

购买总额> 5的客户上次购买

SELECT a.customer, a.sale as max_sale
FROM sales a
INNER JOIN sales c ON a.customer=c.customer
LEFT OUTER JOIN sales b
 ON a.customer=b.customer AND (a.dates < b.dates OR a.dates = b.dates and a.id < b.id)
WHERE b.customer IS NULL
GROUP BY a.id
HAVING COUNT(*) > 5;
Run Code Online (Sandbox Code Playgroud)

与上面其他最大n组的查询一样,销售指数(客户,日期,销售)最适合此查询.它可能无法优化连接和组,因此这将产生临时表.但至少它只会做一个临时表而不是很多.


这些查询足够复杂.您不应该尝试编写可以提供所有这些结果的单个SQL查询.记住Brian Kernighan的经典引用:

每个人都知道调试的难度是首先编写程序的两倍.因此,如果你在编写它时就像你一样聪明,你将如何调试它?