missing chunk number x for toast value x in pg_toast_x
相关知识
- toast是The OverSized Attribute Storage Technique(超尺寸字段存储技术)的缩写,是超长字段在pg中的一种存储方式。
- pg采用的存储默认是每个页面存储固定8Kb大小的数据,并且元组(行记录)不允许跨页面存储,所以并不能直接存储大字段数据。
- 所以toast会将大字段值压缩或者分散为多个物理行来存储。
- pg的部分类型数据支持toast,因为不是所有字段都会产生大字段数据的,完全没必要用到Toast技术(比如date,time,boolean等)。
- 支持Toast的数据类型应当时变长的(variable-length)。
- 当表中字段任何一个有Toast,那这个表都会有这一个相关联的Toast表。
- OID被存储在pg_class.reltoastrelid里面。
- 超出的数值将会被分割成chunks,并最多toast_max_chunk_size 个byte(缺省是2Kb),
- 当存储的行数据超过toast_tuple_threshold值(通常是2kB),就会触发toast存储,这时toast将会压缩或者移动字段值直到超出部分比toast_tuple_targer值小(这个值通常也是2KB)。
相比较普通表(MAIN TABLE),TOAST有额外的三个字段
chunk_id:标识TOAST表的OID字段
chunk_seq:chunk的序列号,与chunk_id的组合唯一索引可以加速访问
chunk_data:存储TOAST表的实际数据toast有4种存储策略:
PLAIN:避免压缩和行外存储。只有那些不需要TOAST策略就能存放的数据类型允许选择(例如int类型),而对于text这类要求存储长度超过页大小的类型,是不允许采用此策略的
EXTENDED:允许压缩和行外存储。一般会先压缩,如果还是太大,就会行外存储
EXTERNA:允许行外存储,但不许压缩。类似字符串这种会对数据的一部分进行操作的字段,采用此策略可能获得更高的性能,因为不需要读取出整行数据再解压。
MAIN:允许压缩,但不许行外存储。不过实际上,为了保证过大数据的存储,行外存储在其它方式(例如压缩)都无法满足需求的情况下,作为最后手段还是会被启动。因此理解为:尽量不使用行外存储更贴切。
举例说明
创建表
postgres=# create table blog(id int, title text, content text);
CREATE TABLE
postgres=# \d+ blog;
Table "public.blog"
Column | Type | Modifiers | Storage | Stats target | Description
---------+---------+-----------+----------+--------------+-------------
id | integer | | plain | |
title | text | | extended | |
content | text | | extended | |可以看到,interger默认TOAST策略为plain,而text为extended。PG资料告诉我们,如果表中有字段需要TOAST,那么系统会自动创建一张TOAST表负责行外存储,那么这张表在哪里?
postgres=# select relname,relfilenode,reltoastrelid from pg_class where relname='blog';
relname | relfilenode | reltoastrelid
---------+-------------+---------------
blog | 16441 | 16444
(1 row)通过上诉语句,我们查到blog表的oid为16441,其对应TOAST表的oid为16444(关于oid和pg_class的概念,请参考PG官方文档),那么其对应TOAST表名则为:pg_toast.pg_toast_16441(注意这里是blog表的oid),我们看下其定义:
postgres=# \d+ pg_toast.pg_toast_16441;
TOAST table "pg_toast.pg_toast_16441"
Column | Type | Storage
------------+---------+---------
chunk_id | oid | plain
chunk_seq | integer | plain
chunk_data | bytea | plainTOAST表有3个字段:
- chunk_id:用来表示特定TOAST值的OID,可以理解为具有同样chunk_id值的所有行组成原表(这里的blog)的TOAST字段的一行数据
- chunk_seq:用来表示该行数据在整个数据中的位置
- chunk_data:实际存储的数据。
现在我们来实际验证下:
postgres=# insert into blog values(1, 'title', '0123456789');
INSERT 0 1
postgres=# select * from blog;
id | title | content
----+-------+------------
1 | title | 0123456789
(1 row)postgres=# select * from pg_toast.pg_toast_16441;
chunk_id | chunk_seq | chunk_data
----------+-----------+------------
(0 rows)可以看到因为content只有10个字符,所以没有压缩,也没有行外存储。然后我们使用如下SQL语句增加content的长度,每次增长1倍,同时观察content的长度,看看会发生什么情况?
postgres=# update blog set content=content||content where id=1;
UPDATE 1
postgres=# select id,title,length(content) from blog;
id | title | length
----+-------+--------
1 | title | 20
(1 row)postgres=# select * from pg_toast.pg_toast_16441;
chunk_id | chunk_seq | chunk_data
----------+-----------+------------
(0 rows)反复执行如上过程,直到pg_toast_16441表中有数据:
postgres=# select id,title,length(content) from blog;
id | title | length
----+-------+--------
1 | title | 327680
(1 row)postgres=# select chunk_id,chunk_seq,length(chunk_data) from pg_toast.pg_toast_16441;
chunk_id | chunk_seq | length
----------+-----------+--------
16439 | 0 | 1996
16439 | 1 | 1773
(2 rows)可以看到,直到content的长度为327680时(已远远超过页大小8K),对应TOAST表中才有了2行数据,且长度都是略小于2K,这是因为extended策略下,先启用了压缩,然后才使用行外存储
下面我们将content的TOAST策略改为EXTERNA,以禁止压缩。
postgres=# alter table blog alter content set storage external;
ALTER TABLEpostgres=# \d+ blog;
Table "public.blog"
Column | Type | Modifiers | Storage | Stats target | Description
---------+---------+-----------+----------+--------------+-------------
id | integer | | plain | |
title | text | | extended | |
content | text | | external | |然后我们再插入一条数据:
postgres=# insert into blog values(2, 'title', '0123456789');
INSERT 0 1
postgres=# select id,title,length(content) from blog;
id | title | length
----+-------+--------
1 | title | 327680
2 | title | 10
(2 rows)然后重复以上步骤,直到TOAST表中产生新的行:
postgres=# update blog set content=content||content where id=2;
UPDATE 1
postgres=# select id,title,length(content) from blog;
id | title | length
----+-------+--------
2 | title | 2560
1 | title | 327680
(2 rows)postgres=# select chunk_id,chunk_seq,length(chunk_data) from pg_toast.pg_toast_16441;
chunk_id | chunk_seq | length
----------+-----------+--------
16447 | 0 | 1996
16447 | 1 | 1773
16448 | 0 | 1996
16448 | 1 | 564
(4 rows)这次我们看到当content长度达到2560(按照官方文档,应该是超过2KB左右),TOAST表中产生了新的2条chunk_id为16448的行,且2行数据的chunk_data的长度之和正好等于2560。
toast的优缺点
1.可以存储超长超大字段,避免之前不能直接存储的限制
2.物理上与普通表是分离的,检索查询时不检索到该字段会极大地加快速度
3.更新普通表时,该表的Toast数据没有被更新时,不用去更新Toast表toast的劣势:
1.对大字段的索引创建是一个问题,有可能会失败,其实通常也不建议在大字段上创建,全文检索倒是一个解决方案。
2.大字段的更新会有点慢,其它DB也存在,通病
报错原因
某张表关联的toast表的data发生损坏。
解决方法
1、 定位是哪张表的toast有问题:
select 2619::regclass;
pg_statistic2、 找到哪个表有问题后,先对该表做一下简单的修复:
REINDEX table pg_toast.pg_toast_2619;
REINDEX table pg_statistic;
VACUUM ANALYZE pg_statistic;3、 定位该表中损坏的数据行。执行
DO $$
declare
v_rec record;
BEGIN
for v_rec in SELECT * FROM pg_statistic loop
raise notice 'Parameter is: %', v_rec.ctid;
raise notice 'Parameter is: %', v_rec;
end loop;
END;
$$
LANGUAGE plpgsql;4、 将第3步中定位的记录删除:
delete from pg_statistic where ctid ='(50,3)';5、 重复执行第3,4步,直到全部有问题的记录被清除。
6、 至此,toast问题就解决完了,解决之后,对数据库进行一次完整的维护或者索引重建。
invalid page header in block x of relation base/x/x
相关知识
- 参数zero_damaged_pages是bool型的。
- 默认是off,意思是系统遇到这类因磁盘、内存等硬件引起的问题就会给出这样一份错误提示。
- 当设置为on时,就可以忽略这些错误报告,并擦除掉这些损坏的数据,没受影响的数据还是正常的。
报错原因
系统检测到磁盘页损坏,并导致postgresql数据取消当前的事务并提交一份错误报告信息。
解决方法
1、 关闭数据库服务器。
2、 编辑postgresql.conf文件,最后一行加入:zero_damaged_pages = on。保存文件,退出。
3、 启动数据库服务器,确认数据库服务是否恢复运行。
4、 关闭数据库服务器。
5、 编辑postgresql.conf文件,去掉最后一行zero_damaged_pages = on。保存文件,退出。
6、 重启数据库。
missing chunk number x for toast value x in pg_toast_x
相关知识
Pg的事务是依靠多版本控制实现的,即一条记录更改前及更改后其实是两条数据,并且都存储在当前表里。这两条记录除了本身的逻辑信息之外,还存有相关事务信息。这些事务信息随着事务的改变而改变。当数据库当前事务id和某一条记录的事务id匹配时,那么这条匹配的记录就会显示。所以,当事务非正常结束时,可能会使某一条记录的事务信息没有更新正确,那么此时我们看到的就可能是两条相同主键的数据。实际上,这是一条数据的两种不同状态。这时就出现了上面的错误。
postgresql 多版本并发控制(MCC)
Postgres用多版本并发控制方式管理事务(Multiversion concurrency control),这点上与Oracle非常相似,后者也采用多版本并发控制机制。
MCC的特点:(通俗点儿)
读不产生独占锁,可以共享,不影响其他事务的读写;
写产生独占锁,数据可以共享,不影响读,但不能同时写;
MCC的目的就是最大化实现并发对数据的访问,postgresql与oracle实现的效果相同,只是方式不同。实质是对事务修改数据的管理方式采用不同方式。
postgresql将修改前后的数据都存储在相同的结构中(新旧版本都存在表中)。两个事务同时访问记录时,通过参考tmin和tmax的标记判断记录的版本,根据版本号与自己当前的事务标识比较,确定自己的数据权限。当事务发生修改或者删除记录后,空间没有立即释放,实质并没有在原来的存储位置上进行更新,而是使用了新的存储空间。系统通过周期性的运行vaccum进程来回收之前的存储空间(与Java虚拟机的垃圾回收机制有点象)。事务提交前,只需要访问原来的数据即可;提交后,系统更新元组的存储标识,直到vaccum进程收回为止。
oracle也是根据事务标识的比较确认数据访问权限。当事务发生修改或者删除记录,oracle将原数据存储在undo段中,事务提交后直接更新相应记录的存储位置,并没有直接使用新的存储空间。oracle的undo段同样用于数据库的介质恢复,在应用完redo日志后(前滚),通过应用undo段中的事务,对数据库进行回滚。
oracle在10g中引入的flashback功能中的flashback drop功能与postgresql的方式就有点类似,只是针对表进行删除标记,并没有释放表所占用的空间,通过对表进行改名。在数据库管理员进行recyclebin操作后,才能释放表所占空间。
postgresql尽管是分配新的空间,对空间的分配很快。在postgresql中也有相应的参数对其进行限制,当更新的数据达到一定的程度时将会直接出发vacuum进行,直接释放掉原来的空间。postgresql与oracle对delete操作产生的空间使用,基本相同。对update操作产生的空间使用不同,相对oracle来说,postgresql可能会产生更多的行迁移或迁移的,这样对性能影响应该是存在的。
报错原因
主键重复,导致重建索引报错。
解决方法
1、 根据报错信息定位问题记录。
2、 将问题记录拷贝出,并判断正确状态的数据行。
3、 将正确的记录拷贝会表里。
4、 对数据库重建索引。
本文详细解析了PostgreSQL中TOAST存储机制的工作原理,包括如何处理超长字段、不同存储策略的选择及其对性能的影响。同时,介绍了如何诊断和解决TOAST相关的常见问题。
744

被折叠的 条评论
为什么被折叠?



