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

时间:2013-02-21 13:56:04

标签: mysql sql performance optimization greatest-n-per-group

在我最近的问题Select information from last item and join to the total amount之后,我在生成表时出现了一些内存问题

我有两张表sales1sales2,如下所示:

  

id |日期|客户|销售

使用此表定义:

CREATE TABLE sales (
    id int auto_increment primary key, 
    dates date,
    customer int,
    sale int
);

sales1sales2具有相同的定义,但sales2在每个字段中都有sale=-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;

问题是:

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

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

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

InternalError: (InternalError) (1205, u'Lock wait timeout exceeded; try restarting transaction')

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

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

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

3 个答案:

答案 0 :(得分:13)

300k行不是一张大桌子。我们经常看到3亿行表。

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

通常情况下,您不需要在一个SQL语句中执行 all 您的工作。将它分解为几个更简单的SQL语句是有好处的:

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

购买数量

SELECT customer, COUNT(sale) AS number_of_purchases
FROM sales 
GROUP BY customer;

销售指数(客户,销售)最适合此查询。

上次购买价值

这是经常出现的greatest-n-per-group问题。

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;

换句话说,尝试将行a与具有相同客户和更长日期的假设行b匹配。如果找不到这样的行,则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;

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

SELECT customer, SUM(sale) AS total_purchases
FROM sales
WHERE sale > 0
GROUP BY customer;

销售指数(客户,销售)最适合此查询。

您应该考虑使用NULL来表示缺少的销售值而不是-1。像SUM()和COUNT()这样的聚合函数忽略NULL,因此您不必使用WHERE子句来排除具有sale&lt;的行。 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;

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

购买总额的客户上次购买&gt; 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;

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


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

  

每个人都知道调试的难度是首先编写程序的两倍。所以,如果你在写作时就像你一样聪明,你将如何调试呢?

答案 1 :(得分:1)

我认为您应该尝试在sales(customer, date)上添加索引。子查询可能是性能瓶颈。

答案 2 :(得分:1)

你可以让这只小狗尖叫。转储整个内连接查询。真。这是一个几乎没有人知道的技巧。

假设dates是日期时间,转换为可排序的字符串,连接您想要的值,最大(或min),子串强制转换。您可能需要调整日期转换功能(这个在MS-SQL中工作),但这个想法可以在任何地方使用:

SELECT customer, count(sale), max_sale = cast(substring(max(convert(char(19), dates, 120) + str(sale, 12, 2)), 20, 12) as numeric(12, 2))
FROM sales a 
group by customer

VOILÀ。如果您需要更多结果列,请执行以下操作:

SELECT yourkey
            , maxval = left(val, N1)                  --you often won't need this
            , result1 = substring(val, N1+1, N2)
            , result2 = substring(val, N1+N2+1, N3)   --etc. for more values
FROM ( SELECT yourkey, val = max(cast(maxval as char(N1))
                               + cast(resultCol1 as char(N2))
                               + cast(resultCol2 as char(N3)) )
       FROM yourtable GROUP BY yourkey ) t

请确保除最后一个字段外的所有字段都有固定长度。这需要一些工作来解决问题,但是非常容易学习和重复。它可以在任何数据库引擎上运行,即使你有排名函数,这通常会明显优于它们。

有关这一非常常见的挑战的更多信息here