Skip to content

Commit 7a143dd

Browse files
committed
added index usage queries and some notes.
1 parent 61923e1 commit 7a143dd

File tree

4 files changed

+179
-2
lines changed

4 files changed

+179
-2
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ duplicate_indexes_fuzzy.sql
5252

5353
Check indexes and looks at whether or not they are potentially duplicates. It does this by checking the columns used by each index, so it reports lots of false duplicates for partial and functional indexes.
5454

55+
needed_indexes.sql
56+
------------------
57+
58+
Checks for tables which are getting too much sequential scan activity and might need additional indexing. Reports in four groups based on table size, number of scans, write activity, and number of existing indexes.
59+
5560
Locks
5661
=====
5762

bloat/index_bloat_check.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ WITH btree_index_atts AS (
1616
JOIN pg_namespace ON pg_namespace.oid = indexclass.relnamespace
1717
JOIN pg_am ON indexclass.relam = pg_am.oid
1818
WHERE pg_am.amname = 'btree' and indexclass.relpages > 0
19-
--AND nspname NOT IN ('pg_catalog','information_schema')
19+
AND nspname NOT IN ('pg_catalog','information_schema')
2020
),
2121
index_item_sizes AS (
2222
SELECT
@@ -86,7 +86,7 @@ raw_bloat AS (
8686
),
8787
format_bloat AS (
8888
SELECT dbname as database_name, nspname as schema_name, table_name, index_name,
89-
round(realbloat) as bloat_pct, round(wastedbytes/(1024^2)::NUMERIC) as bloat_mb,
89+
round(realbloat) as \bloat_pct, round(wastedbytes/(1024^2)::NUMERIC) as bloat_mb,
9090
round(totalbytes/(1024^2)::NUMERIC,3) as index_mb,
9191
round(table_bytes/(1024^2)::NUMERIC,3) as table_mb,
9292
index_scans

indexes/needed_indexes.sql

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
WITH
2+
write_adjust AS (
3+
-- change the below to 1.0 if pg_stats goes back to
4+
-- the creation of the database
5+
SELECT 0.0 AS adjustment
6+
),
7+
index_usage AS (
8+
SELECT sut.relid,
9+
current_database() AS database,
10+
sut.schemaname::text as schema_name,
11+
sut.relname::text AS table_name,
12+
sut.seq_scan as table_scans,
13+
sut.idx_scan as index_scans,
14+
pg_total_relation_size(relid) as table_bytes,
15+
round(sut.n_tup_ins + sut.n_tup_del + sut.n_tup_upd + sut.n_tup_hot_upd) /
16+
(seq_tup_read::NUMERIC + 2), 2) as writes_per_scan
17+
FROM pg_stat_user_tables sut,
18+
write_adjust
19+
),
20+
index_counts AS (
21+
SELECT sut.relid,
22+
count(*) as index_count
23+
FROM pg_stat_user_tables sut LEFT OUTER JOIN pg_indexes
24+
ON sut.schemaname = pg_indexes.schemaname AND
25+
sut.relname = pg_indexes.tablename
26+
GROUP BY relid
27+
),
28+
too_many_tablescans AS (
29+
SELECT 'many table scans'::TEXT as reason,
30+
database, schema_name, table_name,
31+
table_scans, pg_size_pretty(table_bytes) as table_size,
32+
writes_per_scan, index_count, table_bytes
33+
FROM index_usage JOIN index_counts USING ( relid )
34+
WHERE table_scans > 1000
35+
AND table_scans > ( index_scans * 2 )
36+
AND table_bytes > 32000000
37+
AND writes_per_scan < ( 1.0 )
38+
ORDER BY table_scans DESC
39+
),
40+
scans_no_index AS (
41+
SELECT 'scans, few indexes'::TEXT as reason,
42+
database, schema_name, table_name,
43+
table_scans, pg_size_pretty(table_bytes) as table_size,
44+
writes_per_scan, index_count, table_bytes
45+
FROM index_usage JOIN index_counts USING ( relid )
46+
WHERE table_scans > 100
47+
AND table_scans > ( index_scans )
48+
AND index_count < 2
49+
AND table_bytes > 32000000
50+
AND writes_per_scan < ( 1.0 )
51+
ORDER BY table_scans DESC
52+
),
53+
big_tables_with_scans AS (
54+
SELECT 'big table scans'::TEXT as reason,
55+
database, schema_name, table_name,
56+
table_scans, pg_size_pretty(table_bytes) as table_size,
57+
writes_per_scan, index_count, table_bytes
58+
FROM index_usage JOIN index_counts USING ( relid )
59+
WHERE table_scans > 100
60+
AND table_scans > ( index_scans / 10 )
61+
AND table_bytes > 1000000000
62+
AND writes_per_scan < ( 1.0 )
63+
ORDER BY table_bytes DESC
64+
),
65+
scans_no_writes AS (
66+
SELECT 'scans, no writes'::TEXT as reason,
67+
database, schema_name, table_name,
68+
table_scans, pg_size_pretty(table_bytes) as table_size,
69+
writes_per_scan, index_count, table_bytes
70+
FROM index_usage JOIN index_counts USING ( relid )
71+
WHERE table_scans > 100
72+
AND table_scans > ( index_scans / 4 )
73+
AND table_bytes > 32000000
74+
AND writes_per_scan < ( 0.1 )
75+
ORDER BY writes_per_scan ASC
76+
)
77+
SELECT reason, database, schema_name, table_name, table_scans,
78+
table_size, writes_per_scan, index_count
79+
FROM too_many_tablescans
80+
UNION ALL
81+
SELECT reason, database, schema_name, table_name, table_scans,
82+
table_size, writes_per_scan, index_count
83+
FROM scans_no_index
84+
UNION ALL
85+
SELECT reason, database, schema_name, table_name, table_scans,
86+
table_size, writes_per_scan, index_count
87+
FROM big_tables_with_scans
88+
UNION ALL
89+
SELECT reason, database, schema_name, table_name, table_scans,
90+
table_size, writes_per_scan, index_count
91+
FROM scans_no_writes;
92+
93+
94+
95+

indexes/unused_indexes.sql

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
WITH table_scans as (
2+
SELECT relid,
3+
tables.idx_scan + tables.seq_scan as all_scans,
4+
( tables.n_tup_ins + tables.n_tup_upd + tables.n_tup_del ) as writes,
5+
pg_relation_size(relid) as table_size
6+
FROM pg_stat_user_tables as tables
7+
),
8+
all_writes as (
9+
SELECT sum(writes) as total_writes
10+
FROM table_scans
11+
),
12+
indexes as (
13+
SELECT idx_stat.relid, idx_stat.indexrelid,
14+
idx_stat.schemaname, idx_stat.relname as tablename,
15+
idx_stat.indexrelname as indexname,
16+
idx_stat.idx_scan,
17+
pg_relation_size(idx_stat.indexrelid) as index_bytes,
18+
indexdef ~* 'USING btree' AS idx_is_btree
19+
FROM pg_stat_user_indexes as idx_stat
20+
JOIN pg_index
21+
USING (indexrelid)
22+
JOIN pg_indexes as indexes
23+
ON idx_stat.schemaname = indexes.schemaname
24+
AND idx_stat.relname = indexes.tablename
25+
AND idx_stat.indexrelname = indexes.indexname
26+
WHERE pg_index.indisunique = FALSE
27+
),
28+
index_ratios AS (
29+
SELECT schemaname, tablename, indexname,
30+
idx_scan, all_scans,
31+
round(( CASE WHEN all_scans = 0 THEN 0.0::NUMERIC
32+
ELSE idx_scan::NUMERIC/all_scans * 100 END),2) as index_scan_pct,
33+
writes,
34+
round((CASE WHEN writes = 0 THEN idx_scan::NUMERIC ELSE idx_scan::NUMERIC/writes END),2)
35+
as scans_per_write,
36+
pg_size_pretty(index_bytes) as index_size,
37+
pg_size_pretty(table_size) as table_size,
38+
idx_is_btree, index_bytes
39+
FROM indexes
40+
JOIN table_scans
41+
USING (relid)
42+
),
43+
index_groups AS (
44+
SELECT 'Never Used Indexes' as reason, *, 1 as grp
45+
FROM index_ratios
46+
WHERE
47+
idx_scan = 0
48+
and idx_is_btree
49+
UNION ALL
50+
SELECT 'Low Scans, High Writes' as reason, *, 2 as grp
51+
FROM index_ratios
52+
WHERE
53+
scans_per_write <= 1
54+
and index_scan_pct < 10
55+
and idx_scan > 0
56+
and writes > 100
57+
and idx_is_btree
58+
UNION ALL
59+
SELECT 'Seldom Used Large Indexes' as reason, *, 3 as grp
60+
FROM index_ratios
61+
WHERE
62+
index_scan_pct < 5
63+
and scans_per_write > 1
64+
and idx_scan > 0
65+
and idx_is_btree
66+
and index_bytes > 100000000
67+
UNION ALL
68+
SELECT 'High-Write Large Non-Btree' as reason, index_ratios.*, 4 as grp
69+
FROM index_ratios, all_writes
70+
WHERE
71+
( writes::NUMERIC / ( total_writes + 1 ) ) > 0.02
72+
AND NOT idx_is_btree
73+
AND index_bytes > 100000000
74+
ORDER BY grp, index_bytes DESC )
75+
SELECT reason, schemaname, tablename, indexname,
76+
index_scan_pct, scans_per_write, index_size, table_size
77+
FROM index_groups;

0 commit comments

Comments
 (0)