postgresql sqlチューニング入門 実践編(pgcon14j)

53
1 やまそふと PostgreSQL SQL チューニング入門 実践編 PostgreSQL カンファレンス 2014 株式会社アシスト 山田 聡

Upload: satoshi-yamada

Post on 12-Jul-2015

390 views

Category:

Engineering


7 download

TRANSCRIPT

Page 1: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

1

やまそふと

PostgreSQL SQL チューニング入門 実践編

PostgreSQL カンファレンス 2014株式会社アシスト

山田 聡

Page 2: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

2

本セッションについて

"PostgreSQL SQL チューニング入門 入門編"の続きです細かいアクセスパスの説明は省略します

不参加の方は前セッションの資料とあわせて、後で復習をオススメします

入門セッションのため、ゆっくり進行でお送りします

Page 3: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

3

Who am I ?

名前:山田 聡(やまだ さとし)

会社:株式会社アシスト

仕事:PostgreSQL+PPASのサポート (●racleも...)

PostgreSQL歴:3年

興味:機械学習,軽量言語(Python,JS等)

Page 4: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

4

今日の目的

EXPLAIN ANALYZEで問題点を発見できるようになりましょう

Page 5: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

5

アジェンダ

1.実行プランの強制

2.EXPLAIN と EXPLAIN ANALYZE

3.問題解決例

4.まとめ

Page 6: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

6

アジェンダ

1.実行プランの強制

2.EXPLAIN と EXPLAIN ANALYZE

3.問題解決例

4.まとめ

Page 7: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

7

1.実行プランの強制

PostgreSQLには様々なアクセスパスがある

通常はPostgreSQLが最適なパスを選ぶ

(稀に)選んで欲しくないパスになるケースがある

Page 8: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

8

そんな時に役に立つのがそんな時に役に立つのが"実行プランの強制"

Page 9: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

9

1.実行プランの強制

SET enable_演算子 = off;

プランナーがある演算子を使おうとするのを「強く思いとどまらせる」ことができる

SETを行ったセッションのみに影響する

演算子毎に設定可能(ON/OFF)enable_bitmapscan

enable_hashagg

enable_hashjoin

enable_indexscan

enable_indexonlyscan

enable_material

enable_mergejoin

enable_nestloop

enable_seqscan

enable_sort

enable_tidscan

Page 10: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

10

1.実行プランの強制

強く思いとどまらせるとは?

指定したアクセスパスの始動コストに100000000.0を足す

指定したアクセスパスが選択されなくなるわけではない

→例えばseqscanを選択しないようにするとアクセスパスがなくなる可能性があるため 完全に無効化はしない

sampledb=# explain analyze select * from pgbench_accounts;     QUERY PLAN ----------------------------------------------------------------------------------------------- Seq Scan on pgbench_accounts

(cost=10000000000.00..10000025874.00 rows=1000000 width=97) (actual time=0.008..159.306 rows=1000000 loops=1)

Total runtime: 285.398 ms

Page 11: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

11

1.実行プランの強制

プランを強制してみる

sampledb=# explain analyze select * from pgbench_accounts where aid > 1; QUERY PLAN ------------------------------------------------------------------------ Seq Scan on pgbench_accounts

(cost=0.00..28374.00 rows=1000000 width=97) (actual time=0.011..224.378 rows=999999 loops=1)

Filter: (aid > 1) Rows Removed by Filter: 1 Total runtime: 345.456 ms(4 rows)

sampledb=# SET enable_seqscan = off;SETsampledb=# explain analyze select * from pgbench_accounts where aid > 1; QUERY PLAN ------------------------------------------------------------------------ Index Scan using pgbench_accounts_pkey on pgbench_accounts

(cost=0.42..42169.43 rows=1000000 width=97)(actual time=0.041..480.360 rows=999999 loops=1)

Index Cond: (aid > 1) Total runtime: 603.765 ms(3 rows)

初期状態はSeq Scan(cost=28374.00)

変更後はIndex Scan(cost=42169.43)

Page 12: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

12

これさえあればPostgreSQLでもプランが自由自在?

Page 13: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

13

1.実行プランの強制

いつ使うの?プランの切り分け作業

開発時にどうしても特定プランにしたい時

なぜ無闇につかっちゃだめなの?

人はプランナーより賢くない(Tom Laneでもない限り)

しかし、プランナーは推測しかしない

適切なコスト変数の設定を

統計情報更新のため定期的なANALYZEを

まずはEXPLAIN ANALYZEで問題点を把握しましょう

Page 14: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

14

アジェンダ

1.実行プランの強制

2.EXPLAIN と EXPLAIN ANALYZE

3.問題解決例

4.まとめ

Page 15: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

15

2.EXPLAIN と EXPLAIN ANALYZE

EXPLAINプランナーが作成した"最良の"実行計画を確認するコマンド

コストや行数は統計情報を元にした推定EXPLAIN ANALYZE

EXPLAINの出力に追加の情報を加えるオプション

実際にSQLを実行して情報を取得する

負荷のかかるSQLは注意

DMLの変更に注意

"実行時間"や"実際の行数"を取得する

Page 16: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

16

2.EXPLAIN と EXPLAIN ANALYZE

EXPLAIN (ANALYZE)の読み方

実行計画は各ステップをノードとするツリー構成

インデントが深いところから実行

子ノードの結果を親ノードが受ける

コスト・実行時間は子ノードからの累積

Page 17: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

17

実際の結果を見てみましょう

Page 18: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

18

2.EXPLAIN と EXPLAIN ANALYZE

実行SQL:EXPLAIN ANALYZE

SELECT e.empno,d.dname FROM emp e JOIN dept d ON e.deptno=d.deptno ;

Column | Type ----------+----------------------------- empno | integer ename | character varying(10) job | character varying(9) mgr | integer hiredate | timestamp without time zone sal | integer comm | integer deptno | integer

EMP表

Column | Type --------+----------------------- deptno | integer dname | character varying(14) loc | character varying(13)

DEPT表

Page 19: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

19

2.EXPLAIN と EXPLAIN ANALYZE

実行結果

Hash Join(cost=1.09..2.32 rows=4 width=50) (actual time=0.100..0.120 rows=14 loops=1) Hash Cond:(e.deptno = d.deptno)

-> Seq Scan on emp e (cost=0.00..1.14 rows=14 width=8)

(actual time=0.013..0.022 rows=14 loops=1) -> Hash

(cost=1.04..1.04 rows=4 width=50) (actual time=0.024..0.024 rows=4 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 1kB -> Seq Scan on dept d

(cost=0.00..1.04 rows=4 width=50) (actual time=0.011..0.015 rows=4 loops=1)

Page 20: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

20

2.EXPLAIN と EXPLAIN ANALYZE

実行結果をツリーにすると…

Seq Scan on emp ecost=0.00..1.14

time=0.013..0.022

Hash Joincost=1.09..2.32time=0.100..0.120

Hashcost=1.04..1.04time=0.024..0.024

Seq Scan on dept dcost=0.00..1.04

time=0.011..0.015

cost=1.04+0time=0.015+0.009

cost=1.04+1.14+0.5time=0.024+0.022+0.074

Page 21: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

21

2.EXPLAIN と EXPLAIN ANALYZE

EXPLAIN ANALYZESELECT e.empno,d.dname,s.grade FROM emp e JOIN dept d ON e.deptno=d.deptnoJOIN salgrade s on e.sal between s.losal and s.hisalwhere e.job='SALESMAN';

Column | Type ----------+----------------------------- empno | integer ename | character varying(10) job | character varying(9) mgr | integer hiredate | timestamp without time zone sal | integer comm | integer deptno | integer

EMP表

Column | Type --------+----------------------- deptno | integer dname | character varying(14) loc | character varying(13)

DEPT表

Column | Type --------+--------- grade | integer losal | integer hisal | integer

SALGRADE表

Page 22: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

22

2.EXPLAIN と EXPLAIN ANALYZE

もうちょっと複雑な出力結果

Nested Loop(cost=0.00..3.39 rows=1 width=50)(actual time=0.031..0.089 rows=4 loops=1)

Join Filter: ((emp.sal >= s.losal) AND (emp.sal <= s.hisal)) Rows Removed by Join Filter: 16 -> Nested Loop

(cost=0.00..2.26 rows=1 width=54)(actual time=0.022..0.051 rows=4 loops=1) Join Filter: (emp.deptno = d.deptno) Rows Removed by Join Filter: 12 -> Seq Scan on emp

(cost=0.00..1.18 rows=1 width=12)(actual time=0.011..0.018 rows=4 loops=1)Filter: ((job)::text = 'SALESMAN'::text)Rows Removed by Filter: 10

-> Seq Scan on dept d(cost=0.00..1.04 rows=4 width=50)(actual time=0.001..0.003 rows=4 loops=4)

-> Seq Scan on salgrade s(cost=0.00..1.05 rows=5 width=8)(actual time=0.001..0.002 rows=5 loops=4)

Page 23: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

23

2.EXPLAIN と EXPLAIN ANALYZEの違い

実行結果をツリーにすると…

Nested Loop

Nested Loop

Seq Scan on emp Seq Scan on dept d

Seq Scan on dept d x 4

Seq Scan on salgrade s x 4

Seq Scan on salgrade s (cost=0.00..1.05 rows=5 width=8)

(actual time=0.001..0.002 rows=5 loops=4)

Page 24: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

24

2.EXPLAIN と EXPLAIN ANALYZEの違い

EXPLAIN ANALYZEの結果を見るポイント

インデントが深いところから

出力結果は子ノードからの累積

各ステップのcost/rows(見積,実際)/actual timeに注目

疑うべきポイント

actual timeが跳ね上がっているステップは怪しい

rowsが見積もりと離れている箇所は怪しい

costに比べてactual timeが長い箇所は怪しい

Page 25: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

25

実際に問題を解決してみましょう!

Page 26: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

26

アジェンダ

1.実行プランの強制

2.EXPLAIN と EXPLAIN ANALYZE

3.問題解決例

4.まとめ

Page 27: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

27

3.問題解決例

Column | Type --------------+--------- exception_id | integer(primary key) complete | boolean

EXCEPTION表

Column | Type -------------------------+--------- exception_notice_map_id | integer exception_id | integer notice_id | integer

EXCEPTION_NOTICE_MAP表

complete列の分布

TRUE

FALSE

IndexIndex

● indexは両表のexception_id列のみ作成

● complete列はFalseのデータが1%未満

(1000行/10000000行)

Page 28: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

28

3.問題解決例

実行するSQL

EXPLAIN ANALYZESELECT exception_id,exception_notice_map_idFROM exceptionJOIN exception_notice_map USING (exception_id)WHERE complete is false and notice_id=3;

Page 29: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

29

3.問題解決例

結果Hash Join (cost=175782.31..405182.03 rows=53172 width=8) (actual time=1834.952..1844.389 rows=9 loops=1) Hash Cond: (exception.exception_id = exception_notice_map.exception_id) -> Seq Scan on exception (cost=0.00..144263.00 rows=5000000 width=4) (actual time=789.879..790.120 rows=1000 loops=1) Filter: (complete IS FALSE) Rows Removed by Filter: 9999000 -> Hash (cost=174037.01..174037.01 rows=106344 width=8) (actual time=1044.821..1044.821 rows=100202 loops=1) Buckets: 4096 Batches: 4 Memory Usage: 690kB -> Seq Scan on exception_notice_map (cost=0.00..174037.01 rows=106344 width=8) (actual time=0.081..991.670 rows=100202 loops=1) Filter: (notice_id = 3) Rows Removed by Filter: 9900798

Total runtime: 1844.486 ms

Page 30: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

30

● 状況整理

– 最上位のノードはrows=9 →9行戻すSQL

– 結合はHash Join– 処理時間は1844.486 ms(約2秒)

もっと早くならないかな?

Page 31: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

31

3.問題解決例

見積との差をチェック

Hash Join (cost=175782.31..405182.03 rows=53172 width=8) (actual time=1834.952..1844.389 rows=9 loops=1) Hash Cond: (exception.exception_id = exception_notice_map.exception_id) -> Seq Scan on exception (cost=0.00..144263.00 rows=5000000 width=4) (actual time=789.879..790.120 rows=1000 loops=1) Filter: (complete IS FALSE) Rows Removed by Filter: 9999000 -> Hash (cost=174037.01..174037.01 rows=106344 width=8) (actual time=1044.821..1044.821 rows=100202 loops=1) Buckets: 4096 Batches: 4 Memory Usage: 690kB -> Seq Scan on exception_notice_map (cost=0.00..174037.01 rows=106344 width=8) (actual time=0.081..991.670 rows=100202 loops=1) Filter: (notice_id = 3) Rows Removed by Filter: 9900798

Total runtime: 1844.486 ms

OK!

OK!

ずれてる!?

Page 32: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

32

3.問題解決例

exception表でcompelete is Falseの行は

5000000行くらいかな結合相手も行が多いしたくさん

もどりそうだからHashJoinしよう

(cost=0.00..144263.00 rows=5000000 width=4)(actual time=789.879..790.120 rows=1000 loops=1)

1000行しかなかった…統計情報が古い気がする…

プランナー プランナー

Page 33: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

33

そうだ、ANALYZE、しよう

Page 34: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

34

3.問題解決例

ANALYZE exceptionしてみた

Nested Loop (cost=0.43..152601.93 rows=11 width=8) (actual time=792.030..794.257 rows=9 loops=1) -> Seq Scan on exception (cost=0.00..144262.43 rows=1000 width=4) (actual time=790.677..790.885 rows=1000 loops=1) Filter: (complete IS FALSE) Rows Removed by Filter: 9999000 -> Index Scan using idx_nmap_exception_id on exception_notice_map (cost=0.43..8.33 rows=1 width=8) (actual time=0.003..0.003 rows=0 loops=1000) Index Cond: (exception_id = exception.exception_id) Filter: (notice_id = 3) Rows Removed by Filter: 1 Total runtime: 817.182 ms

ずれがなくなった!!

早くなった!1844.486 ms→817.182 ms

Page 35: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

35

ANALYZEで最新の統計を使いましょう!

Page 36: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

36

3.問題解決例

再度結果を確認

Nested Loop (cost=0.43..152601.93 rows=11 width=8) (actual time=792.030..794.257 rows=9 loops=1) -> Seq Scan on exception (cost=0.00..144262.43 rows=1000 width=4) (actual time=790.677..790.885 rows=1000 loops=1) Filter: (complete IS FALSE) Rows Removed by Filter: 9999000 -> Index Scan using idx_nmap_exception_id on exception_notice_map (cost=0.43..8.33 rows=1 width=8) (actual time=0.003..0.003 rows=0 loops=1000) Index Cond: (exception_id = exception.exception_id) Filter: (notice_id = 3) Rows Removed by Filter: 1 Total runtime: 817.182 ms

1%未満の行にSeq Scanでアクセスしている

complete列の分布

TRUE

FALSE

Page 37: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

37

3.問題解決例

Seq Scanを辞めたいならINDEXを張るのが定石

でもcomplete列はTrue/Falseの2種類しかない

カーディナリティが低いのでINDEX作成の負荷が心配

INDEXを使って欲しいのがFalseの時だけ

INDEXをつけるのは難しいかな・・・?

Page 38: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

38

そうだ部分インデックスがあるじゃないか!

CREATE INDEX idx_is_complete ON exception(complete) WHERE complete IS false;

Page 39: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

39

3.問題解決例

部分インデックスとは

条件を満たす行のみを保持するインデックス

頻出値にインデックスを付けずに済むため

インデックスのサイズが小さいインデックスの更新処理が発生しにくいので

更新パフォーマンスが有利

Page 40: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

40

3.問題解決例

部分INDEX作ったら...

Nested Loop (cost=0.71..8347.79 rows=11 width=8) (actual time=0.266..5.241 rows=9 loops=1) -> Index Scan using idx_is_complete on exception (cost=0.28..8.29 rows=1000 width=4) (actual time=0.073..0.680 rows=1000 loops=1) Index Cond: (complete = false) -> Index Scan using idx_nmap_exception_id on exception_notice_map (cost=0.43..8.33 rows=1 width=8) (actual time=0.004..0.004 rows=0 loops=1000) Index Cond: (exception_id = exception.exception_id) Filter: (notice_id = 3) Rows Removed by Filter: 1

Total runtime: 5.286 ms

Index Scanが使われるようになった!

817.182 ms->5.286 ms160倍早い!

Page 41: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

41

PostgreSQLの色々な機能を活用しよう!・部分インデックス・Materialized View 等(細かい部分はマニュアルで)

Page 42: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

42

アジェンダ

1.実行プランの強制

2.EXPLAIN と EXPLAIN ANALYZE

3.問題解決例

4.まとめ

Page 43: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

43

4.まとめ

EXPLAIN ANALYZEで問題を探すなら

インデントが深いところから

見積もりがずれているところから

時間が伸びているところから

Page 44: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

44

4.まとめ

問題に対処するなら

見積もりがおかしかったら→ANALYZE してみましょう

アクセス行数が少ないのにSeq Scanだったら→インデックスを検討しましょう

PostgreSQLの機能を活用しましょう

最新のPostgreSQLを使いましょう

Page 45: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

45

それでも解決しない時はどうすればいいのだろう?

Page 46: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

46

4.まとめ

メーリングリストに投稿してみましょう

まず自分でデバッグしてみる

PostgreSQLのバージョンを書く

VACUUMとANALYZEを正確に実行してあること

EXPLAIN ANALYZEの結果を必ず書く

クエリ、テーブル、データもできれば含める

[email protected] (英語)[email protected] (日本語)

Page 47: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

47

ご清聴ありがとうございました

参考資料(サイト)Explaining Explain ~ PostgreSQLの実行計画を読む ~ http://lets.postgresql.jp/documents/technical/query_tuning/explaining_explain_ja.pdf/view内部を知って業務に活かす PostgreSQL研究所第4回 http://www2b.biglobe.ne.jp/~caco/webdb-pdfs/vol29.pdf Robert Haas blog http://rhaas.blogspot.com/2011/10/index-only-scans-weve-got-em.html問合せ最適化インサイド http://www.slideshare.net/ItagakiTakahiro/ss-4656848象と戯れ http://postgresql.g.hatena.ne.jp/umitanuki/20110425/1303752697Explaining Explain 第2回 http://www.postgresql.jp/wg/shikumi/study20_materialsExplaining Explain 第3回 http://www.postgresql.jp/wg/shikumi/study21_materials

参考資料(書籍)PostgreSQL 全機能バイブル(技術評論社)PostgreSQL 設計・運用計画の鉄則(技術評論社)

Page 48: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

48

おまけ

実際の運用に際して有用な機能

auto_explain(contribモジュール)

自動的にexplainしてくれる

自動的にログに書いてくれる

実行時間等の条件を指定可能

psqlで試せないSQLでも取得可能

Page 49: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

49

おまけ

マテリアライズドビューが効くケース

EXPLAIN ANALYZESELECT notice_id,count(*) as countFROM exceptionJOIN exception_notice_map USING (exception_id)GROUP BY notice_id order BY count;

Page 50: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

50

おまけ

マテリアライズドビューが効くケース

Sort (cost=864852.14..864852.64 rows=200 width=4) (actual time=78419.788..78419.803 rows=100 loops=1) Sort Key: (count(*)) Sort Method: quicksort Memory: 20kB -> HashAggregate (cost=864842.50..864844.50 rows=200 width=4) (actual time=78419.706..78419.736 rows=100 loops=1) -> Hash Join (cost=303459.50..814837.50 rows=10000000 width=4) (actual time=27756.900..75436.393 rows=10000000 loops=1) Hash Cond: (exception_notice_map.exception_id = exception.exception_id) -> Seq Scan on exception_notice_map (cost=0.00..149035.00 rows=10000000 width=8) (actual time=0.040..2860.579 rows=10000000 loops=1) -> Hash (cost=144263.00..144263.00 rows=10000000 width=4) (actual time=27575.905..27575.905 rows=10000000 loops=1) Buckets: 8192 Batches: 256 Memory Usage: 929kB -> Seq Scan on exception (cost=0.00..144263.00 rows=10000000 width=4) (actual time=0.021..2511.089 rows=10000000 loops=1)

Total runtime: 78419.966 ms

OK!

OK!

OK!

OK!

たぶんOK

たぶんOK。。。だけど遅すぎる

Page 51: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

51

そうだマテリアライズドビューがあるじゃないか!※9.3〜

CREATE MATERIALIZED VIEW exception_notice_summaryASSELECT notice_id,count(*) as countFROM exceptionJOIN exception_notice_map USING (exception_id)GROUP BY notice_id ;

Page 52: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

52

おまけ

マテリアライズドビューとは

実データを保持するビュー

計算済みのデータを保持するため、問合せ負荷が低い

最新の状態を反映するには定期的なリフレッシュが必須

DWH系の処理に向いている

Page 53: PostgreSQL SQLチューニング入門 実践編(pgcon14j)

 

53

おまけ

結果

Sort (cost=135.34..140.19 rows=1940 width=12) (actual time=0.112..0.141 rows=100 loops=1) Sort Key: count Sort Method: quicksort Memory: 20kB -> Seq Scan on exception_notice_summary (cost=0.00..29.40 rows=1940 width=12) (actual time=0.010..0.046 rows=100 loops=1)

Total runtime: 0.187 ms

explain analyze SELECT * FROM exception_notice_summaryORDER BY count;