-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtext-vs-varchar-toast.sql
203 lines (166 loc) · 9.87 KB
/
text-vs-varchar-toast.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
--text vs varchar(n) + TOAST
CREATE DATABASE IF NOT EXISTS _test_storage;
--переключиться на БД _test_storage
--Создаём таблицы с типами колонок text и varchar с указанием длинны. И наполняем их одинаковыми данными
DROP TABLE IF EXISTS test_datatype_text;
CREATE TABLE test_datatype_text
(id serial
,name TEXT
);
INSERT INTO test_datatype_text
(name)
SELECT md5(generate_series::text)
FROM pg_catalog.generate_series(1, 120);
DROP TABLE IF EXISTS test_datatype_varchar;
CREATE TABLE test_datatype_varchar
(id serial
,name varchar(32)
);
INSERT INTO test_datatype_varchar
(name)
SELECT md5(generate_series::text)
FROM pg_catalog.generate_series(1, 120);
--Проверяем количество строк в каждой таблице
SELECT count(*), 'test_datatype_text' AS table_name FROM test_datatype_text
UNION ALL
SELECT count(*), 'test_datatype_varchar' AS table_name FROM test_datatype_varchar;
--count table_name
--120 test_datatype_text
--120 test_datatype_varchar
--проверяем максимальную и минимальную длину колонок text и varchar(32)
SELECT 'test_datatype_text' AS table_name, MAX(LENGTH(name)) AS MAX_LENGTH, MIN(LENGTH(name)) AS MIN_LENGTH, MAX(bit_length(name)) AS MAX_bit_length, MAX(char_length(name)) AS MAX_char_length, MAX(octet_length(name)) AS MAX_octet_length FROM test_datatype_text
UNION ALL
SELECT 'test_datatype_varchar' AS table_name, MAX(LENGTH(name)) AS MAX_LENGTH, MIN(LENGTH(name)) AS MIN_LENGTH, MAX(bit_length(name)) AS MAX_bit_length, MAX(char_length(name)) AS MAX_char_length, MAX(octet_length(name)) AS MAX_octet_length FROM test_datatype_varchar;
--table_name max_length min_length max_bit_length max_char_length max_octet_length
--test_datatype_text 32 32 256 32 32
--test_datatype_varchar 32 32 256 32 32
--взглянем на сами данные и размещение кортежей на страницах
SELECT ctid, * FROM test_datatype_text LIMIT 10;
SELECT ctid, * FROM test_datatype_text OFFSET 115 LIMIT 10;
SELECT ctid, * FROM test_datatype_varchar LIMIT 10;
SELECT ctid, * FROM test_datatype_varchar OFFSET 115 LIMIT 10;
--Проверим соединение таблиц по текстовому полю
SELECT count(*)
FROM test_datatype_text AS t
INNER JOIN test_datatype_varchar AS v
ON v.name = t.name
--count
--120
--смотрим на физические страницы и строки
SELECT 'test_datatype_text' AS table_name, * FROM page_header(get_raw_page('test_datatype_text', 0));
--table_name lsn checksum flags lower upper special pagesize version prune_xid
--test_datatype_text 3A/4612E5E0 0 0 504 512 8 192 8 192 4 0
SELECT 'test_datatype_varchar' AS table_name, * FROM page_header(get_raw_page('test_datatype_varchar', 0));
--table_name lsn checksum flags lower upper special pagesize version prune_xid
--test_datatype_varchar 3A/46133A48 0 0 504 512 8 192 8 192 4 0
--смотрим знимаемое место только таблицей и таблицей с индексами и попутным
--тут видим, что таблица без ограничения длины занимает больше места, потому что появилась область TOAST
SELECT 'test_datatype_text' AS table_name, pg_size_pretty(pg_relation_size('test_datatype_text')), pg_size_pretty(pg_total_relation_size('test_datatype_text'));
--table_name pg_size_pretty pg_size_pretty
--test_datatype_text 8192 bytes 16 kB
SELECT 'test_datatype_varchar' AS table_name, pg_size_pretty(pg_relation_size('test_datatype_varchar')), pg_size_pretty(pg_total_relation_size('test_datatype_varchar'));
--table_name pg_size_pretty pg_size_pretty
--test_datatype_varchar 8192 bytes 8192 bytes
--вот таким нехитрым способом находим это попутное
SELECT tb.oid
, tb.relname
, tb.relpages
, pg_size_pretty(pg_relation_size(tb.relname::text)) AS table_size
, sec.oid
, sec.relname
, sec.relpages
, pg_size_pretty(pg_total_relation_size(tb.relname::text)) AS table_size
FROM pg_catalog.pg_class AS tb
LEFT JOIN pg_catalog.pg_class AS sec
ON sec.oid = tb.reltoastrelid
WHERE tb.relname = 'test_datatype_text'
OR tb.relname = 'test_datatype_varchar'
;
--А теперь добавим данных, чтобы получить 2 страницы
INSERT INTO test_datatype_text
(name)
SELECT md5(generate_series::text)
FROM pg_catalog.generate_series(1, 120);
INSERT INTO test_datatype_varchar
(name)
SELECT md5(generate_series::text)
FROM pg_catalog.generate_series(1, 120);
--проверим, что у нас там в конце таблицы
SELECT ctid, * FROM test_datatype_text ORDER BY id DESC LIMIT 10;
SELECT ctid, * FROM test_datatype_varchar ORDER BY id DESC LIMIT 10;
--смотрим на физические страницы и строки
SELECT 'test_datatype_text' AS table_name, * FROM page_header(get_raw_page('test_datatype_text', 1))
UNION ALL
SELECT 'test_datatype_varchar' AS table_name, * FROM page_header(get_raw_page('test_datatype_varchar', 1));
--table_name lsn checksum flags lower upper special pagesize version prune_xid
--test_datatype_text 3A/46295328 0 0 504 512 8 192 8 192 4 0
--test_datatype_varchar 3A/46298220 0 0 504 512 8 192 8 192 4 0
--смотрим знимаемое место только таблицей и таблицей с индексами и попутным
--увидим, что резервирование идёт только для одной страницы TOAST
SELECT 'test_datatype_text' AS table_name, pg_size_pretty(pg_relation_size('test_datatype_text')), pg_size_pretty(pg_total_relation_size('test_datatype_text'))
UNION ALL
SELECT 'test_datatype_varchar' AS table_name, pg_size_pretty(pg_relation_size('test_datatype_varchar')), pg_size_pretty(pg_total_relation_size('test_datatype_varchar'));
--table_name pg_size_pretty pg_size_pretty
--test_datatype_text 16 kB 48 kB
--test_datatype_varchar 16 kB 40 kB
--накинем больших данных, колонку больше 8Кб
--важно, чтобы строка была как можно более уникальной, так как Postgres по умолчанию использует стратегию хранения EXTENDED со сжатием (сначала пытается сжать, а потом уже отправить в TOAST) и например repeat(MD5, 900) не подходил, так как ужимался
INSERT INTO test_datatype_text
(name)
SELECT string_agg(value,'_')
FROM (
SELECT sha256(generate_series::TEXT::bytea)::TEXT AS value
FROM pg_catalog.generate_series(1, 300)
) AS t
;
--а в этом случае ожидаемо получим ошибку: ERROR: value too long for type character varying(32)
INSERT INTO test_datatype_varchar
(name)
SELECT string_agg(value,'_')
FROM (
SELECT sha256(generate_series::TEXT::bytea)::TEXT AS value
FROM pg_catalog.generate_series(1, 300)
) AS t
;
--проверим, что у нас там в конце таблицы
SELECT ctid, * FROM test_datatype_text ORDER BY id DESC LIMIT 10;
--ctid id name
--(2,1) 241 \x6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b_..._\x983bd614bb5afece5ab3b6023f71147cd7b6bc2314f9d27af7422541c6558389
--(1,120) 240 da4fb5c6e93e74d3df8527599fa62642
--проверяем максимальную и минимальную длину колоноки text
SELECT 'test_datatype_text' AS table_name, MAX(LENGTH(name)) AS MAX_LENGTH, MIN(LENGTH(name)) AS MIN_LENGTH, MAX(bit_length(name)) AS MAX_bit_length, MAX(char_length(name)) AS MAX_char_length, MAX(octet_length(name)) AS MAX_octet_length FROM test_datatype_text
--table_name max_length min_length max_bit_length max_char_length max_octet_length
--test_datatype_text 20 099 32 160 792 20 099 20 099
--смотрим знимаемое место только таблицей и таблицей с индексами и попутным
SELECT 'test_datatype_text' AS table_name, pg_size_pretty(pg_relation_size('test_datatype_text')), pg_size_pretty(pg_total_relation_size('test_datatype_text'));
--table_name pg_size_pretty pg_size_pretty
--test_datatype_text 24 kB 112 kB
--смотрим на физические страницы и строки
SELECT 'test_datatype_text' AS table_name, * FROM page_header(get_raw_page('test_datatype_text', 2));
--table_name lsn checksum flags lower upper special pagesize version prune_xid
--test_datatype_text 3A/462FF470 0 0 28 8 144 8 192 8 192 4 0
--гдянем ещё раз на наши таблицы
SELECT tb.oid
, tb.relname
, tb.relpages
, pg_size_pretty(pg_relation_size(tb.relname::text)) AS table_size
, sec.oid
, sec.relname
, sec.relpages
, pg_size_pretty(pg_total_relation_size(tb.relname::text)) AS table_size
FROM pg_catalog.pg_class AS tb
LEFT JOIN pg_catalog.pg_class AS sec
ON sec.oid = tb.reltoastrelid
WHERE tb.relname = 'test_datatype_text'
OR tb.relname = 'test_datatype_varchar'
;
--из предыдущего запроса копируем название TOAST таблицы и посмотрим её
SELECT * FROM pg_toast.pg_toast_475897;
--chunk_id - ссылка на значение, подвергнутое TOAST-обработке
--chunk_seq - последовательный номер порции данных, представляющей часть значения
--chunk_data - порция данных (сами данные)
--посмотрим размер больших строк и количесво порций
SELECT chunk_id, count(chunk_seq), pg_size_pretty(sum(octet_length(chunk_data)::bigint)) FROM pg_toast.pg_toast_475897 GROUP BY chunk_id;
--chunk_id count pg_size_pretty
--475 908 11 20 kB
--SELECT pg_database_size('_test_storage'), pg_size_pretty(pg_database_size('_test_storage'));