postgres数据存储优化

前言

postgres开源数据库目前用的人很多。从应用开发层面的开发工程师来说,首先能用,其次是用好,再次是深究原理和源码怎么才能用好。今天我们来深究一下postgres存储的原理,怎样才能用好postgres关于存储和查询。

起因

问题的起因是小伙伴在使用pg的jsonb格式时发现更新json数据会导致数据库存储空间急速攀升。首先小伙伴用的json数据比较大,这个小伙伴能力还不错,在使用pg的时候能关注到存储方面的内容,并进行多次测试验证。从一般的理解数据库的更新应该不会导致存储空间的增大(json数据内容差不多)。
问题已经摆在面前,那么先重复盘一下这个问题,再分析问题,最后解决吧。为了简单起见。我们用int和varchar代替json字段先进行测试,排查掉是字段原因引起的。

问题重新

第1步:建表并查询
create table varchar_table (id varchar(48),text varchar(64));
--查询表的大小
select pg_table_size('varchar_table');
--结果为0
第2步:插入数据测试
insert into varchar_table(id,text) values ('1','1');
select pg_table_size('varchar_table');
--结果为8192
insert into varchar_table(id,text) values ('1','1');
select pg_table_size('varchar_table');
--结果为8192

发现第一次插入数据后,表大小变成了8192比特,插入第二条数据没变。第一条的数据应该没有8k那么大,这个可能是表初始大小吧。那我们继续测

第3步:加大数据量测
insert into varchar_table (id,text) select generate_series(1,100,1 )::TEXT,uuid_generate_v4();
select pg_table_size('varchar_table');
--结果为8192
insert into varchar_table (id,text) select generate_series(1,100,1 )::TEXT,uuid_generate_v4();
select pg_table_size('varchar_table');
--结果为40960

发现第三步的第一次插入表存储大小还是没变,第二次变了,变成了第一次的5倍。

第4步:试试update
update varchar_table set ID = '1';
select pg_table_size('varchar_table');
--结果为57344

验证了小伙伴的问题,只不过我们用的不是json字段是varchar字段,但他会同样出现。说明这个和字段无关。

第5步:试试delete或truncate
delete from varchar_table;
select pg_table_size('varchar_table');
--结果为16384
truncate table varchar_table;
--结果为 0

没有数据也占了空间,使用truncate 才真正的释放了表空间。这是为啥清表数据推挤使用truncate table的原因。
根据上面的事实依据。我道下原理吧。update世界上分了4步操作,1select出需要更新的数据,2内存中变更数据,3insert新数据,4必要的时候使用HOT技术(Heap Only Tuple)增加旧行和新行的指针。
可以看到update的操作非但没有删除数据,反而还增加了一个指针。
Hot技术(Heap Only Tuple):就行没有删除,插入了新行,如果列上有索引需要更新索引,会导致性能下降,hot技术是解决更新后旧行和新行数据在同一个数据块内,通过旧行新增指针到新行,查询的时候根据指针找到新行。这样达到不用更新索引的作用。

问题的症结找到了。那有解决是什么?

使用vacuum或者vacuum full。
vacuum的作用就是删除旧行更新索引空出空间。但这个空间还是被表持有,新行插入会优先使用这部分空间。
vacuum full是从表空间回收这部分空间。这个命令生产环境慎用,是整个数据库加锁的。
我们重复上述2-4步后执行如下sql

vacuum;
--结果为65536
vacuum full;
--结果为16384

从事实数据分析数据库

心细的小伙伴肯定在纠结上述数据中8192和40960这个两个空间怎么来的会不会和列的数量和长度有关。我们再测试一下:

create table int_table (id int);
insert into int_table(id) values (1);
select pg_table_size('int_table');
--结果为8192
insert into int_table (id) select generate_series(1,300,1 );
--结果为40960
vacuum full;
--结果为16384

看过数据库文档的小伙伴肯定有个印象数据库分页技术。pg的数据库默认分页是8k,对就是这里的8192。第一次插入数据时,数据库直接给表一个分页的存储空间,但数据增加到一点的阈值,分页变成了5页,即40960。经过表空间回收后剩下2页的空间。其他的疑问小伙伴们自行查找解决吧。

数据库存储还有TOAST技术

TOAST是“The Oversized-Attribute Storage Technique”的缩写,主要用于存储一个大字段的值。PG不允许一行数据跨页存储,那么对于超长的行数据,PG就会启动TOAST,具体就是采用压缩和切片的方式。如果启用了切片,实际数据存储在另一张系统表的多个行中,这张表就叫TOAST表,这种存储方式叫行外存储。在深入细节之前,我们要先了解,在PG中每个表字段有四种TOAST的策略:

  • PLAIN:避免压缩和行外存储。只有那些不需要TOAST策略就能存放的数据类型允许选择(例如int类型),而对于text这类要求存储长度超过页大小的类型,是不允许采用此策略的
  • EXTENDED:允许压缩和行外存储。一般会先压缩,如果还是太大,就会行外存储
  • EXTERNA:允许行外存储,但不许压缩。类似字符串这种会对数据的一部分进行操作的字段,采用此策略可能获得更高的性能,因为不需要读取出整行数据再解压。
  • MAIN:允许压缩,但不许行外存储。不过实际上,为了保证过大数据的存储,行外存储在其它方式(例如压缩)都无法满足需求的情况下,作为最后手段还是会被启动。因此理解为:尽量不使用行外存储更贴切。 现在我们通过实际操作来研究TOAST的细节:
mydb=#create table json_table (id varchar, data jsonb);
mydb=# \d+ json_table;
                               数据表 "public.json_table"
 栏位 |       类型        | Collation | Nullable | Default |   存储   | 统计目标 | 描述
------+-------------------+-----------+----------+---------+----------+----------+------
 id   | character varying |           |          |         | extended |          |
 data | jsonb             |           |          |         | extended |          |

mydb=# select relname,relfilenode,reltoastrelid from pg_class where relname='json_table';
  relname   | relfilenode | reltoastrelid
------------+-------------+---------------
 json_table |       87334 |         87337

mydb=# \d+ pg_toast.pg_toast_87334;
TOAST 数据表 "pg_toast.pg_toast_87334"
    栏位    |  类型   | 存储
------------+---------+-------
 chunk_id   | oid     | plain
 chunk_seq  | integer | plain
 chunk_data | bytea   | plain

mydb=# alter table json_table alter data set storage external;

总结

对数据库进行黑盒测试的数据,可以发现。在pg中使用update大数据列例如json等数据的频繁更新,需要适时的进行数据整理。不然你的硬盘不一定撑不了多久。以下通过开头的通过psql执行。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容