8.0のウィンドウ機能は本当に香りがよい
1.問題の説明
最近、私はすべてのmysql低速クエリログをデータベースに投げ込み、それらを一元的に表示して、ビジネス部門に公開しています。また、ビジネス部門の学生がそれぞれのビジネスで低速SQLを表示して最適化するのにも便利です。過去1〜2週間の遅いクエリの数の変化をカウントし、ビジネスクラスメートのより直感的なデータ比較を提供し、最近の期間の遅いクエリの数の変化を理解する定期的なレポート生成の機能を追加しました。 。したがって、次のSQLがあります。
select hostname_max , db_max, sum(ts_cnt) as 1W
(select ifnull(sum(t1.ts_cnt),0) as ts_cnt from global_query_review_history t1 where
t1.hostname_max=t2.hostname_max and t1.ts_min>= date_sub(now(), interval 14 day) and
t1.ts_max<= date_sub(now(), interval 7 day)) AS 2W
from global_query_review_history t2 where
ts_min>= date_sub(now(), interval 7 day)
group by hostname_max, db_max
order by 1W desc limit 20;
現在のglobal_query_review_historyテーブルには約25,000レコードがあります。このSQLには1.16秒かかりますが、これは明らかに遅すぎます。SQL実行計画は次のとおりです。
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: t2
partitions: NULL
type: ALL
possible_keys: ts_min
key: NULL
key_len: NULL
ref: NULL
rows: 25198
filtered: 41.09
Extra: Using where; Using temporary; Using filesort
*************************** 2. row ***************************
id: 2
select_type: DEPENDENT SUBQUERY
table: t1
partitions: NULL
type: ref
possible_keys: hostname_max,ts_min
key: hostname_max
key_len: 258
ref: func
rows: 20
filtered: 14.90
Extra: Using where
サブクエリを実行する必要があることがわかります(JOINに自動的に最適化することはできません)。
SQL実行後のステータス統計:
+-----------------------+--------+
| Variable_name | Value |
+-----------------------+--------+
| Handler_read_first | 0 |
| Handler_read_key | 17328 |
| Handler_read_last | 0 |
| Handler_read_next | 809121 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 25380 |
+-----------------------+--------+
完全なテーブルスキャンに加えて、インデックスに基づく複数の行ごとのスキャン(Handler_read_next = 809121、サブクエリによって発生)がわかります。
2.SQLの最適化
上記のSQLの主なボトルネックは、ネストされたサブクエリにあり、サブクエリが削除された場合でも、テーブル全体のスキャンでさえ非常に高速です。
[[email protected]]> select ...
...
20 rows in set (0.08 sec)
[[email protected]]> show status like 'handler%read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 16910 |
| Handler_read_last | 0 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 25380 |
+-----------------------+-------+
SQL最適化の難しさは、当然、Songhua氏のことを最初に考えました。私がMySQL 8.0を使用していることを知った後、彼はそれをウィンドウ関数に基づく記述に変換するのを手伝いました。
select hostname_max , db_max,
sum( case when ts_min>= date_sub(now(), interval 7 day) then ts_cnt end ) as 1W,
ifnull(sum(case when ts_min>= date_sub(now(), interval 14 day)
and ts_max<= date_sub(now(), interval 7 day) then ts_cnt end ) over(partition by hostname_max),0) 2W
from global_query_review_history t2
where ts_min>= date_sub(now(), interval 14 day)
group by hostname_max, db_max
order by 1W desc limit 20;
実行計画をもう一度見てください。
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t2
partitions: NULL
type: ALL
possible_keys: ts_min
key: NULL
key_len: NULL
ref: NULL
rows: 25198
filtered: 44.88
Extra: Using where; Using temporary; Using filesort
新しいSQLはよりトリッキーで、データを1回読み取るだけで、ウィンドウ関数を使用して必要な統計を直接計算できます。利用可能なインデックスはありますが、スキャンするデータ量が比較的多いため、最終的にはフルテーブルスキャンになります。新しいSQLの時間のかかるステータス統計は次のとおりです。
20 rows in set (0.08 sec)
[[email protected]]> show status like 'handler%read%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 24396 |
| Handler_read_last | 0 |
| Handler_read_next | 0 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 886 |
| Handler_read_rnd_next | 26703 |
+-----------------------+-------+
以前のSQLとは大きなギャップがあり、最適化効果が活用されます。
全文は終わりました。
MySQL8.0をお楽しみください:)
参考文献
コードをスキャンし、Songhuaによる「詳細なSQLプログラミングの開発と最適化」のコースに従います。
または、記事の最後にある[オリジナルを読む]をクリックして直接移動します