web-dev-qa-db-ja.com

MySQLの最適化-年の列のグループ化-一時テーブル、filesortの使用

600,000レコードのトランザクションテーブルがあります。ダッシュボードのカウントを会計年度ごとに一覧表示する必要があります。使用されるテーブルはMyISAMです。取引日(_tran_date_)のインデックスを追加してみました。インデックスを使用していても、一時テーブルとファイルソートのために時間がかかる一時テーブルを作成します。クエリ時間を改善するためにクエリを最適化する方法はありますか?

 SELECT COUNT(*)AS cnt、CASE WHEN MONTH(tran_date)> = 3 
 THEN concat(YEAR(tran_date)、 '-'、YEAR(tran_date)+1)
 ELSE concat(YEAR(tran_date)-1、 '-'、YEAR(tran_date))
 END AS Financial_year 
 FROM `transactions1` 
 WHERE tran_date> = '2010-06 -01 '
 GROUP BY Financial_year 
 
行0〜4を表示(合計5、クエリに1.2095秒かかりました)
 id select_type table typepossible_keys key key_len ref rows Extra 
 1 SIMPLE transaction1 range PRIMARY、tran_date tran_date 8 NULL 346485 where;インデックスを使用します。一時的な使用; filesort 
の使用
キー名タイプ一意のパックフィールドカーディナリティ照合
 PRIMARY BTREEはいいいえtran_date 205720 A 
 tran_ID 617162 A 
 coupon_No BTREEいいえいいえcoupon_No 617162 A 
 account_typeBTREEいいえいいえaccount_type 3 A 
 prodCode BTREEいいえいいえprodCode 430 A 
 tran_date 308581 A 
 tran_date BTREEいいえいいえtran_date 205720 A 
 cust_ID BTREEいいえいいえcust_ID 3265 A 
 tran_date 308581 A 
 account_type 308581 A 
 points_earned 617162 A 

更新:

分割されていないものと比較してそれほど役に立たないパーティションを追加しようとしました。この場合、レプリケーションはこのテーブルの読み取りに役立ちますか?データを読み取るときに、(日付関数を使用して)日付に基づいてさらにグループ化されます。

編集:

クエリを変更し、クエリの実行時間を短縮しました。私が使用したクエリは、

 SELECT SUM(count)
 FROM(
 SELECT COUNT(*)AS count、
 CASE WHEN MONTH(tran_date)> = 3 
 THEN concat(YEAR(tran_date)、 '-'、YEAR(tran_date)+1)
 ELSE concat(YEAR(tran_date)-1、 '-'、YEAR(tran_date))
 END AS format_date 
 FROMトランザクション1 
 GROUP BY tran_date 
)AS s 
 GROUP BY format_date 
 
行0〜4を表示(合計5、クエリに0.5636秒かかりました)
 id select_type table typepossible_keys key key_len ref rows Extra 
 1 PRIMARY&ltderived2> ALL NULL NULL NULL NULL 229676一時的な使用filesort 
 2派生トランザクション1インデックスNULL tran_date 8 NULL 617162の使用インデックス
の使用

しかし、使用するとき

 SELECT COUNT(*)AS count、
 CASE WHEN WHEN MONTH(tran_date)> = 3 
 THEN concat(YEAR(tran_date)、 '-'、YEAR(tran_date)+1 )
 ELSE concat(YEAR(tran_date)-1、 '-'、YEAR(tran_date))
 END AS format_date 
 FROMトランザクション1 
 GROUP BY tran_date 
 
行0〜29を表示(合計229,676、クエリに0.0006秒かかりました)

派生テーブルでSUM(count)を使用しないと、時間が短縮されます。 MySQLでサブクエリを使用せずに合計を取得する他の方法はありますか、またはインデックスを取得するためにサブクエリを最適化できますか?.

6
Dinesh

改善の余地はあまりありません。

追加したインデックスは、WHERE句での範囲一致(type => range、key => tran_date)に使用され、カバリングインデックス(extra => using index)として使用されているため、おそらく非常に役立ちました。行データをフェッチするためにテーブルをシークする必要がなくなります。

ただし、関数を使用してグループのFinancial_Year値を作成するため、「ファイルソートの使用」と「一時的な使用」の両方を回避することはできません。しかし、それは本当の問題ではありません。実際の問題は、MONTH(tran_date)を346,485回評価し、YEAR(tran_date)を少なくともその回数だけ評価していることです... 1秒あたりの700,000回の関数呼び出しはそれほど悪くないようです。

プランB:私は間違いなく冗長データを保存するのが好きではなく、アプリケーションにそれを維持する責任を負わせることに夢中になっています...これらの統計情報を最新に保つために、transactions1テーブルで後挿入/更新/削除トリガーを使用します。

もちろん、このオプションにはコストがかかります-トランザクションの更新/挿入/削除にかかる時間に加えて...しかし、ダッシュボードの統計を1200ミリ秒より長く待つこともコストになります。したがって、今すぐ支払うか、後で支払うかが問題になる場合があります。

3

より効率的な方法は、動的SQLまたは外部言語を使用してクエリを作成し、(tran_date)のインデックスを使用することだと思います。

SELECT cnt
     , CONCAT(year, '-', year+1) AS financial_year
FROM 
    ( SELECT COUNT(*) AS cnt,  2010 AS year
      FROM transactions1
      WHERE tran_date >= '2010-06-01'
        AND tran_date <  '2011-04-01'
    UNION ALL
      SELECT COUNT(*), 2011 
      FROM transactions1
      WHERE tran_date >= '2011-04-01'
        AND tran_date <  '2012-04-01'
    UNION ALL
      SELECT COUNT(*), 2012
      FROM transactions1
      WHERE tran_date >= '2012-03-01'
        AND tran_date <  '2013-04-01'
    ) AS tmp
ORDER BY year ;

FiscalCalendarテーブルを導入することもできます:

CREATE TABLE Fiscal_Calendar
  ( fiscal_year SMALLINT NOT NULL
  , start_date DATE NOT NULL
  , next_start_date DATE NOT NULL
  , PRIMARY KEY (fiscal_year)
  , INDEX start_date_IDX (start_date)
  ) ;

INSERT INTO Fiscal_Calendar
  (fiscal_year, start_date, next_start_date) 
VALUES
  (1900, '1900-04-01', '1901-04-01'),
  ---
  (2099, '2099-04-01', '2100-04-01') ; 

そして、それを使用します(動的SQLは必要ありません):

SELECT 
    ( SELECT COUNT(*)
      FROM transactions1 AS tr
      WHERE tr.tran_date >= fc.start_date
        AND tr.tran_date <  fc.next_start_date
    ) AS cnt,
    CONCAT(fc.year, '-', fc.year+1) AS financial_year
FROM 
        Fiscal_Calendar AS fc
    JOIN
        ( SELECT year
          FROM Fiscal_Calendar
          WHERE start_date <= '2010-06-01'
          ORDER BY start_date DESC
        ) AS param
      ON  fc.year >= param.year 
      AND fc.year <= YEAR(NOW())
ORDER BY 
    fc.year ;
2
ypercubeᵀᴹ

tran_dateの年を3か月減らしてグループ化してみます。 year1-year2の最終的なフォーマットは、グループ化された結果セットで実行できます。

SELECT
  count,
  CONCAT(finyear, '-', finyear + 1) AS formattedfinyear
FROM (
  SELECT
    COUNT(*) AS count,
    YEAR(trand_date - INTERVAL 3 MONTH) AS finyear
  FROM transactions1
  GROUP BY finyear
) s
;
1
Andriy M