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でサブクエリを使用せずに合計を取得する他の方法はありますか、またはインデックスを取得するためにサブクエリを最適化できますか?.
改善の余地はあまりありません。
追加したインデックスは、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ミリ秒より長く待つこともコストになります。したがって、今すぐ支払うか、後で支払うかが問題になる場合があります。
より効率的な方法は、動的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 ;
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
;