3

最近の質問Select information from last item and join to total amountに従って、テーブルの生成中にメモリの問題が発生しています

私は2つのテーブルを持っていてsales1sales2このようにしています:

ID | 日付 | 顧客 | セール

このテーブル定義では:

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

sales1sales2同じ定義をsales2持ちますがsale=-1、すべてのフィールドにあります。顧客は、いずれのテーブルにも、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 年の情報だけでなく、すべての年をまとめた情報も取得する必要があります。

私がちょうど 1 年間やると、すべての情報を保存するのに約 2 ~ 3 分かかります。

しかし、すべての年から情報を収集しようとすると、データベースがクラッシュし、次のようなメッセージが表示されます。

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

このような巨大なテーブルを結合するのは、データベースにとって負担が大きすぎるようです。クエリをexplain実行すると、ほぼすべての時間の割合がcreating tmp table.

収集したデータを 4 分の 1 に分割することを考えました。3 か月ごとに結果を取得し、それを結合して並べ替えます。しかし、この最後の結合と並べ替えは、データベースにとってはやり過ぎになると思います。

では、テーブル構造を変更できない限り、これらのクエリを最適化するために専門家は何を推奨しますか?

4

3 に答える 3

13

300k 行は巨大なテーブルではありません。3 億行のテーブルがよく見られます。

クエリの最大の問題は、相関サブクエリを使用しているため、外側のクエリの各行に対してサブクエリを再実行する必要があることです。

多くの場合、すべての作業を 1 つの SQL ステートメントで行う必要はありません。複数の単純な SQL ステートメントに分割することには利点があります。

  • コーディングが容易になります。
  • 最適化が容易になります。
  • デバッグが容易になります。
  • 読みやすい。
  • 新しい要件を実装する必要がある場合の保守が容易になります。

購入数

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

このクエリには、sales(customer,sale) のインデックスが最適です。

最終購入額

これは、頻繁に発生するグループごとの最大 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;

つまり、行を、同じ顧客でより大きな日付を持つa架空の行に一致させようとします。bそのような行が見つからない場合はa、その顧客にとって最大の日付である必要があります。

このクエリには、sales(customer,dates,sale) のインデックスが最適です。

その最大の日に顧客に対して複数の販売がある場合、このクエリは顧客ごとに複数の行を返します。同点を打破するには、別の列を見つける必要があります。自動インクリメントの主キーを使用する場合、一意であることが保証され、時系列で増加する傾向があるため、タイ ブレーカーとして適しています。

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;

このクエリには、sales(customer,sale) のインデックスが最適です。

-1 の代わりに NULL を使用して、欠落している販売値を示すことを検討する必要があります。SUM() や COUNT() などの集計関数は NULL を無視するため、WHERE 句を使用して、sale < 0 の行を除外する必要はありません。


Re: あなたのコメント

私が今持っているのは、年、四半期、total_sale ((年、四半期) のペアに関して) および販売のフィールドを持つテーブルです。私が収集したいのは、特定の期間に関する情報です。この四半期、四半期、2011 年などです。情報は、上位の顧客、売り上げの多い顧客などに分割する必要があります。 total_purchases が 5 を超えていますか?

2012 年第 4 四半期の上位 5 顧客

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;

実際のデータに対してテストしたいのですが、このクエリには売上 (年、四半期、顧客、売上) のインデックスが最適だと思います。

合計購入数が 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;

上記のグループあたり最大 n クエリと同様に、このクエリには sales(customer,dates,sale) のインデックスが最適です。おそらく、結合とグループ化の両方を最適化することはできないため、一時テーブルが発生します。しかし、少なくとも、多くの代わりに 1 つの一時テーブルのみを実行します。


これらのクエリは非常に複雑です。これらすべての結果が得られる単一の SQL クエリを作成しようとするべきではありません。Brian Kernighan の古典的な引用を思い出してください。

デバッグは、そもそもプログラムを書くよりも 2 倍難しいことは誰もが知っています。それで、あなたがそれを書くときにできる限り賢いなら、どうやってそれをデバッグするのでしょうか?

于 2013-02-21T15:24:44.270 に答える
1

あなたはこの子犬を悲鳴を上げることができます。内部結合クエリ全体をダンプします。本当に。これは事実上誰も知らないように見えるトリックです。

dates日時であると仮定して、それをソート可能な文字列に変換し、必要な値、 max(またはmin)、substringcastを連結します。日付変換関数(これは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

最後のフィールドを除くすべてのフィールドの長さが固定されていることを確認してください。これは頭​​を動かすのに少し手間がかかりますが、非常に学習可能で繰り返し可能です。これはどのデータベースエンジンでも機能し、ランク関数がある場合でも、これは多くの場合、それらを大幅に上回ります。

この非常に一般的な課題の詳細については、こちらをご覧ください。

于 2013-02-21T14:59:28.287 に答える
1

にインデックスを追加してみてくださいsales(customer, date)。サブクエリはおそらくパフォーマンスのボトルネックです。

于 2013-02-21T14:10:53.843 に答える