This repository has been archived by the owner on Dec 5, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 64
/
bloated_tables_and_indexes.sql
205 lines (199 loc) · 8 KB
/
bloated_tables_and_indexes.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
CREATE SCHEMA IF NOT EXISTS zz_utils;
GRANT USAGE ON SCHEMA zz_utils TO public;
/*
zz_utils.get_bloated_tables() - a security workaround wrapper around sql from https://wiki.postgresql.org/wiki/Show_database_bloat
*/
--drop function if exists zz_utils.get_bloated_tables(boolean, int);
DO $OUTER$
DECLARE
l_current_db text := current_database();
l_sproc_text text := $SQL$
CREATE OR REPLACE FUNCTION zz_utils.get_bloated_tables(
IN p_order_by_bloat_factor boolean DEFAULT FALSE,
IN p_limit int DEFAULT 100,
OUT schema_name text,
OUT table_name text,
OUT bloat_factor numeric,
OUT wasted_bytes numeric,
OUT wasted_bytes_pretty text)
RETURNS SETOF record AS
$$
SELECT
schemaname::text,
tblname::text,
round(bloat_ratio::numeric, 1),
bloat_size::numeric,
pg_size_pretty(bloat_size::int8)
FROM (
SELECT current_database(), schemaname, tblname, bs*tblpages AS real_size,
(tblpages-est_num_pages)*bs AS bloat_size,
CASE WHEN tblpages - est_num_pages > 0
THEN round((100 * (tblpages - est_num_pages)/tblpages::float)::numeric, 1)
ELSE 0
END AS bloat_ratio
FROM (
SELECT ceil(reltuples/((bs-page_hdr)/tpl_size)) + ceil(toasttuples/4) AS est_num_pages,
tblpages, bs, tblid, schemaname, tblname, heappages, toastpages, is_na
FROM (
SELECT
( 4 + tpl_hdr_size + tpl_data_size + (2*ma)
- CASE WHEN tpl_hdr_size%ma = 0 THEN ma ELSE tpl_hdr_size%ma END
- CASE WHEN ceil(tpl_data_size)::int%ma = 0 THEN ma ELSE ceil(tpl_data_size)::int%ma END
) AS tpl_size, bs - page_hdr AS size_per_block, (heappages + toastpages) AS tblpages, heappages,
toastpages, reltuples, toasttuples, bs, page_hdr, tblid, schemaname, tblname, is_na
FROM (
SELECT
tbl.oid AS tblid, ns.nspname AS schemaname, tbl.relname AS tblname, tbl.reltuples,
tbl.relpages AS heappages, coalesce(toast.relpages, 0) AS toastpages,
coalesce(toast.reltuples, 0) AS toasttuples,
current_setting('block_size')::numeric AS bs,
CASE WHEN version()~'mingw32' OR version()~'64-bit|x86_64|ppc64|ia64|amd64' THEN 8 ELSE 4 END AS ma,
24 AS page_hdr,
23 + CASE WHEN MAX(coalesce(null_frac,0)) > 0 THEN ( 7 + count(*) ) / 8 ELSE 0::int END
+ CASE WHEN tbl.relhasoids THEN 4 ELSE 0 END AS tpl_hdr_size,
sum( (1-coalesce(s.null_frac, 0))
* coalesce(
CASE
WHEN t.typlen = -1 THEN
CASE WHEN s.avg_width < 127
THEN s.avg_width + 1 ELSE s.avg_width + 4
END
WHEN t.typlen = -2 THEN s.avg_width + 1
ELSE t.typlen
END
, 1024)) AS tpl_data_size,
bool_or(att.atttypid = 'pg_catalog.name'::regtype) AS is_na
FROM pg_attribute AS att
JOIN pg_type AS t ON att.atttypid = t.oid
JOIN pg_class AS tbl ON att.attrelid = tbl.oid
JOIN pg_namespace AS ns ON ns.oid = tbl.relnamespace
JOIN pg_stats AS s ON s.schemaname=ns.nspname
AND s.tablename = tbl.relname AND s.inherited=false AND s.attname=att.attname
LEFT JOIN pg_class AS toast ON tbl.reltoastrelid = toast.oid
WHERE att.attnum > 0 AND NOT att.attisdropped
AND tbl.relkind = 'r'
GROUP BY 1,2,3,4,5,6,7,8,9,10, tbl.relhasoids
ORDER BY 2,3
) AS s
) AS s2
) AS s3
WHERE NOT is_na
) AS o
ORDER BY
CASE WHEN p_order_by_bloat_factor THEN bloat_ratio ELSE bloat_size END DESC
LIMIT
p_limit
$$ LANGUAGE sql VOLATILE SECURITY DEFINER;
$SQL$;
BEGIN
EXECUTE l_sproc_text;
EXECUTE 'GRANT EXECUTE ON FUNCTION zz_utils.get_bloated_tables(boolean, int) TO public';
END;
$OUTER$;
/*
zz_utils.get_bloated_indexes() - a security workaround wrapper around sql from https://wiki.postgresql.org/wiki/Show_database_bloat
*/
--drop function if exists zz_utils.get_bloated_indexes(boolean, int);
DO $OUTER$
DECLARE
l_current_db text := current_database();
l_sproc_text text := $SQL$
CREATE OR REPLACE FUNCTION zz_utils.get_bloated_indexes(
IN p_order_by_bloat_factor boolean DEFAULT FALSE,
IN p_limit int DEFAULT 100,
OUT schema_name text,
OUT table_name text,
OUT index_name text,
OUT bloat_factor numeric,
OUT wasted_bytes numeric,
OUT wasted_bytes_pretty text)
RETURNS SETOF record AS
$$
SELECT
schemaname::text,
tblname::text,
idxname::text,
round(bloat_ratio::numeric, 1),
bloat_size::numeric,
pg_size_pretty(bloat_size::int8)
FROM (
SELECT current_database(), nspname AS schemaname, tblname, idxname, bs*(relpages-est_pages)::bigint AS extra_size,
100 * (relpages-est_pages)::float / relpages AS extra_ratio,
fillfactor, bs*(relpages-est_pages_ff) AS bloat_size,
100 * (relpages-est_pages_ff)::float / relpages AS bloat_ratio,
is_na
FROM (
SELECT
coalesce(
1 +
ceil(reltuples/floor((bs-pageopqdata-pagehdr)/(4+nulldatahdrwidth)::float))
, 0) AS est_pages,
coalesce(
1 + ceil(reltuples/floor((bs-pageopqdata-pagehdr)*fillfactor/(100*(4+nulldatahdrwidth)::float)))
, 0) AS est_pages_ff,
bs, nspname, table_oid, tblname, idxname, relpages, fillfactor, is_na
FROM (
SELECT maxalign, bs, nspname, tblname, idxname, reltuples, relpages, relam, table_oid, fillfactor,
( index_tuple_hdr_bm +
maxalign - CASE
WHEN index_tuple_hdr_bm%maxalign = 0 THEN maxalign
ELSE index_tuple_hdr_bm%maxalign
END
+ nulldatawidth + maxalign - CASE
WHEN nulldatawidth = 0 THEN 0
WHEN nulldatawidth::integer%maxalign = 0 THEN maxalign
ELSE nulldatawidth::integer%maxalign
END
)::numeric AS nulldatahdrwidth, pagehdr, pageopqdata, is_na
FROM (
SELECT
i.nspname, i.tblname, i.idxname, i.reltuples, i.relpages, i.relam, a.attrelid AS table_oid,
current_setting('block_size')::numeric AS bs,
fillfactor,
CASE
WHEN version() ~ 'mingw32' OR version() ~ '64-bit|x86_64|ppc64|ia64|amd64' THEN 8
ELSE 4
END AS maxalign,
24 AS pagehdr,
16 AS pageopqdata,
CASE WHEN max(coalesce(s.null_frac,0)) = 0
THEN 2
ELSE 2 + (( 32 + 8 - 1 ) / 8)
END AS index_tuple_hdr_bm,
sum( (1-coalesce(s.null_frac, 0)) * coalesce(s.avg_width, 1024)) AS nulldatawidth,
max( CASE WHEN a.atttypid = 'pg_catalog.name'::regtype THEN 1 ELSE 0 END ) > 0 AS is_na
FROM pg_attribute AS a
JOIN (
SELECT nspname, tbl.relname AS tblname, idx.relname AS idxname, idx.reltuples, idx.relpages, idx.relam,
indrelid, indexrelid, indkey::smallint[] AS attnum, coalesce(substring(
array_to_string(idx.reloptions, ' ')
from 'fillfactor=([0-9]+)')::smallint, 90) AS fillfactor
FROM pg_index
JOIN pg_class idx ON idx.oid=pg_index.indexrelid
JOIN pg_class tbl ON tbl.oid=pg_index.indrelid
JOIN pg_namespace ON pg_namespace.oid = idx.relnamespace
WHERE pg_index.indisvalid AND tbl.relkind = 'r' AND idx.relpages > 0
) AS i ON a.attrelid = i.indexrelid
JOIN pg_stats AS s ON s.schemaname = i.nspname
AND ((s.tablename = i.tblname AND s.attname = pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE))
OR (s.tablename = i.idxname AND s.attname = a.attname))
JOIN pg_type AS t ON a.atttypid = t.oid
WHERE a.attnum > 0
GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9
) AS s1
) AS s2
JOIN pg_am am ON s2.relam = am.oid WHERE am.amname = 'btree'
) AS sub
WHERE NOT is_na
) AS o
ORDER BY
CASE WHEN p_order_by_bloat_factor THEN bloat_ratio ELSE bloat_size END DESC
LIMIT
p_limit
$$ LANGUAGE sql VOLATILE SECURITY DEFINER;
$SQL$;
BEGIN
EXECUTE l_sproc_text;
EXECUTE 'GRANT EXECUTE ON FUNCTION zz_utils.get_bloated_indexes(boolean, int) TO public';
END;
$OUTER$;